diff --git a/.actrc b/.actrc deleted file mode 100644 index 99e6b7ecc..000000000 --- a/.actrc +++ /dev/null @@ -1,3 +0,0 @@ -# Configuration file for nektos/act. -# See https://github.com/nektos/act#configuration --P ubuntu-latest=shivammathur/node:latest diff --git a/.editorconfig b/.editorconfig index 84f918ed5..8ccb6afe7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,9 +2,7 @@ # editorconfig.org # WordPress Coding Standards -# https://make.wordpress.org/core/handbook/coding-standards/ - -# From https://github.com/WordPress/wordpress-develop/blob/trunk/.editorconfig with a couple of additions. +# http://make.wordpress.org/core/handbook/coding-standards/ root = true @@ -14,13 +12,12 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = tab +indent_size = 4 -[{*.yml,*.feature,.jshintrc,*.json}] +[*.{json,yml,feature}] indent_style = space indent_size = 2 -[*.md] -trim_trailing_whitespace = false - -[{*.txt,wp-config-sample.php}] -end_of_line = crlf +[composer.json] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes index 288222187..97cb2461f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,22 +1,3 @@ # Auto detect text files and perform EOL normalization * text=auto eol=lf tests/data/*-win.php eol=crlf - -# Don't show bundled libraries as source files in GitHub -bundle/**/* linguist-vendored - -# Exclude development and CI-related files from distribution archives - -/.editorconfig export-ignore -/.actrc export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.mailmap export-ignore -/.behat.yml export-ignore -/phpcs.xml.dist export-ignore -/phpstan.neon.dist export-ignore - -/tests export-ignore -/features export-ignore -/.github export-ignore -/utils/phpstan export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index f69375fb2..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @wp-cli/committers diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..4bbca121a --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,40 @@ + diff --git a/.github/ISSUE_TEMPLATE/1-BUG_REPORT.md b/.github/ISSUE_TEMPLATE/1-BUG_REPORT.md deleted file mode 100644 index 21c2ada60..000000000 --- a/.github/ISSUE_TEMPLATE/1-BUG_REPORT.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: "\U0001F41B Bug Report" -about: "Something isn't working as expected" -title: '' -labels: 'i: bug' -assignees: '' - ---- - -## Bug Report - - - -- [ ] Yes, I reviewed the [contribution guidelines](https://make.wordpress.org/cli/handbook/contributing/). -- [ ] Yes, more specifically, I reviewed the guidelines on [how to write clear bug reports](https://make.wordpress.org/cli/handbook/bug-reports/). - -**Describe the current, buggy behavior** - -A clear and concise description of the behavior that produces an incorrect result or error. - -Remember: more information is better. Please provide context on how you're running the command to make sure we're all on the same page when reasoning about this. - -**Describe how other contributors can replicate this bug** - -- a list of -- steps to replicate -- the error condition - -```js -// You can also use code snippets if needed. -``` - -**Describe what you expect as the correct outcome** - -A clear and concise description of what you expected to happen (or code). - -**Let us know what environment you are running this on** - -``` -(Paste the output of "wp cli info" into this box) -``` - -**Provide a possible solution** - -If you happen to have a suggestion on how to fix this bug, please tell us in here. - -Just leave this section out if you don't know how to fix it. - -**Provide additional context/screenshots** - -Add any other context about the problem here. - -If applicable, add screenshots to help explain (you can just drag&drop images into the GitHub issue). diff --git a/.github/ISSUE_TEMPLATE/2-FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/2-FEATURE_REQUEST.md deleted file mode 100644 index 8133232d9..000000000 --- a/.github/ISSUE_TEMPLATE/2-FEATURE_REQUEST.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: "\U0001F195 Feature Request" -about: "I have a suggestion for missing functionality or improvements" -title: '' -labels: 'i: enhancement' -assignees: '' - ---- - -## Feature Request - - - -- [ ] Yes, I reviewed the [contribution guidelines](https://make.wordpress.org/cli/handbook/contributing/). - -**Describe your use case and the problem you are facing** - -A clear and concise description of what you are actually trying to do, and in what way the current version of WP-CLI is not up to the task. - -**Describe the solution you'd like** - -A clear and concise description of what you want to happen. Add any considered drawbacks if you can think of any. diff --git a/.github/ISSUE_TEMPLATE/3-SUPPORT_REQUEST.md b/.github/ISSUE_TEMPLATE/3-SUPPORT_REQUEST.md deleted file mode 100644 index 4802abdf1..000000000 --- a/.github/ISSUE_TEMPLATE/3-SUPPORT_REQUEST.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: "\U00002753 Support Request" -about: "I have a question about how to use WP-CLI" -title: '' -labels: 'i: state:unsupported' -assignees: '' - ---- - ---------------^ Click "Preview" for a nicer view! - -GitHub issues are meant for enhancement requests and specific, reproducible bugs, not for general support questions. For support options, please review [https://wp-cli.org/#support](https://wp-cli.org/#support). - -The easiest way to get support is to join us in the [`#cli`](https://wordpress.slack.com/messages/C02RP4T41) channel on the [Make WordPress Slack Team](https://make.wordpress.org/chat/). diff --git a/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md b/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md deleted file mode 100644 index c4181df8e..000000000 --- a/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -name: "\U0001F680 Regular Release Checklist" -about: "\U0001F512 Maintainers only: create a checklist for a regular release process" -title: 'Release checklist for v2.x.x' -labels: 'i: scope:distribution' -assignees: '' - ---- -# Regular Release Checklist - v2.x.x - -### Preparation - -- [ ] Mention on Slack that a release is being prepared - - People should wait with updating until the announcement. Before that, things are still in motion. - -- [ ] Verify all tests pass in the [automated test suite](https://github.com/wp-cli/automated-tests) - -- [ ] Regenerate command and internal API docs - - Command and internal API docs need to be regenerated before every major release, because they're meant to correlate with the release. - - ``` - git clone git@github.com:wp-cli/handbook.git - cd handbook - WP_CLI_PACKAGES_DIR=bin/packages ../wp-cli-bundle/vendor/bin/wp handbook gen-all - ``` - -- [ ] Fetch the list of contributors (from within the [`wp-cli/wp-cli-dev`](https://githubcom/wp-cli/wp-cli-dev/) project repo) - - From within the `wp-cli/wp-cli-dev` project repo, use `wp maintenance contrib-list` to generate a list of release contributors: - - ``` - GITHUB_TOKEN= wp maintenance contrib-list --format=markdown - ``` - - This script identifies pull request creators from `wp-cli/wp-cli-bundle`, `wp-cli/wp-cli`, `wp-cli/handbook`, and all bundled WP-CLI commands (e.g. `wp-cli/*-command`). - - For `wp-cli/wp-cli-bundle`, `wp-cli/wp-cli` and `wp-cli/handbook`, the script uses the currently open release milestone. - - For all bundled WP-CLI commands, the script uses all closed milestones since the last WP-CLI release (as identified by the version present in the `composer.lock` file). If a command was newly bundled since last release, contributors to that command will need to be manually added to the list. - - The script will also produce a total contributor and pull request count you can use in the release post. - -- [ ] Generate release notes for all packages (from within the [`wp-cli/wp-cli-dev`](https://githubcom/wp-cli/wp-cli-dev/) project repo) - - From within the `wp-cli/wp-cli-dev` project repo, use `wp maintenance release-notes` to generate the release notes: - - ``` - GITHUB_TOKEN= wp maintenance release-notes - ``` - -- [ ] Draft release post on the [make.wordpress.org CLI blog](https://make.wordpress.org/cli/wp-admin/post-new.php) - - Use previous release blog posts as inspiration. - - Use the contributor list and changelog from the previous steps in the blog post. - - Note down the permalink already now, as it will be needed in later steps. - -### Updating WP-CLI - -#### In [`wp-cli/wp-cli`](https://github.com/wp-cli/wp-cli/) - -- [ ] Create a branch called `prepare-x-x-x` to prepare the version bump. - -- [ ] Update the WP-CLI version mention in `wp-cli/wp-cli`'s `README.md` ([ref](https://github.com/wp-cli/wp-cli/issues/3647)). - -- [ ] Lock `php-cli-tools` version (if needed) - `php-cli-tools` is sometimes set to `dev-main` during the development cycle. During the WP-CLI release process, `composer.json` should be locked to a specific version. `php-cli-tools` may need a new version tagged as well. - -- [ ] Ensure that the contents of [VERSION](https://github.com/wp-cli/wp-cli/blob/master/VERSION) in `wp-cli/wp-cli` are changed to latest. - -- [ ] Submit the PR and merge it once all checks are green. - -- [ ] Create a Git tag for the new version. **Do not create a GitHub _release_ just yet**. - -#### In [`wp-cli/wp-cli-bundle`](https://github.com/wp-cli/wp-cli-bundle/) - -- [ ] Create a branch called `release-x-x-x` to prepare the release PR. **Branch name is very important here!** - -- [ ] Lock the framework version in `composer.json` - - The version constraint of the `wp-cli/wp-cli` framework requirement is usually set to `"dev-main"`. Set it to the stable tagged release that represents the version to be published. - - As an example, if releasing version 2.1.0 of WP-CLI, the `wp-cli/wp-cli-bundle` should require `"wp-cli/wp-cli": "^2.1.0"`. - - ``` - composer require wp-cli/wp-cli:^2.1.0 - ``` - -### Updating the Phar build - -- [ ] Create a PR from the `release-x-x-x` branch in `wp-cli/wp-cli-bundle` and merge it. This will trigger the `wp-cli-release.*` builds. - -- [ ] Create a Git tag and push it. **Do not create a GitHub _release_ just yet**. - -- [ ] Create a stable [Phar build](https://github.com/wp-cli/builds/tree/gh-pages/phar): - - ``` - cd wp-cli/builds/phar - cp wp-cli-release.phar wp-cli.phar - cp wp-cli-release.manifest.json wp-cli.manifest.json - md5 -q wp-cli.phar > wp-cli.phar.md5 - shasum -a 256 wp-cli.phar | cut -d ' ' -f 1 > wp-cli.phar.sha256 - shasum -a 512 wp-cli.phar | cut -d ' ' -f 1 > wp-cli.phar.sha512 - ``` - -- [ ] Sign the release with GPG (see ): - - ``` - gpg --output wp-cli.phar.gpg --default-key releases@wp-cli.org --sign wp-cli.phar - gpg --output wp-cli.phar.asc --default-key releases@wp-cli.org --detach-sig --armor wp-cli.phar - ``` - - Note: The GPG key for `releases@wp-cli.org` has to be shared amongst maintainers. - -- [ ] Verify the signature with `gpg --verify wp-cli.phar.asc wp-cli.phar` - -- [ ] Perform one last sanity check on the Phar by ensuring it displays its information - - ``` - php wp-cli.phar --info - ``` - -- [ ] Commit the Phar and its hashes to the `builds` repo - - ``` - git status - git add . - git commit -m "Update stable to v2.x.0" - ``` - -- [ ] Create actual releases on GitHub: Make sure to upload the previously generated Phar from the `builds` repo. - - ``` - cp wp-cli.phar wp-cli-2.x.0.phar - cp wp-cli.phar.gpg wp-cli-2.x.0.phar.gpg - cp wp-cli.phar.asc wp-cli-2.x.0.phar.asc - cp wp-cli.phar.md5 wp-cli-2.x.0.phar.md5 - cp wp-cli.phar.sha512 wp-cli-2.x.0.phar.sha256 - cp wp-cli.phar.sha512 wp-cli-2.x.0.phar.sha512 - cp wp-cli.manifest.json wp-cli-2.x.0.manifest.json - ``` - - Do this for both [`wp-cli/wp-cli`](https://github.com/wp-cli/wp-cli/) and [`wp-cli/wp-cli-bundle`](https://github.com/wp-cli/wp-cli-bundle/) - -- [ ] Verify Phar release artifact - - ``` - $ wp cli update - You are currently using WP-CLI version 2.12.0-alpha-d2bfea9. Would you like to update to 2.12.0? [y/n] y - Downloading from https://github.com/wp-cli/wp-cli/releases/download/v2.12.0/wp-cli-2.12.0.phar... - sha512 hash verified: fe19025cc113142492a3ca68dd93d20ba4164e5ecb3c0a0d86a9db7e06b917201120763fa2b8256addeaa9cb745b2b8bef8e8d74a697230e30ef681f13e09186 - New version works. Proceeding to replace. - Success: Updated WP-CLI to 2.12.0. - $ wp cli version - WP-CLI 2.12.0 - $wp eval 'echo \WP_CLI\Utils\http_request( "GET", "https://api.wordpress.org/core/version-check/1.6/" )->body;' --skip-wordpress - - ``` - -### Verify the Debian and RPM builds - -- [ ] In the [`wp-cli/builds`](https://github.com/wp-cli/builds) repository, verify that the Debian and RPM builds exist - - **Note:** Right now, they are actually already generated automatically before all the tagging happened. - -- [ ] Change symlink of `deb/php-wpcli_latest_all.deb` to point to the new stable version. - -### Updating the Homebrew formula (should happen automatically) - -- [ ] Follow this [example PR](https://github.com/Homebrew/homebrew-core/pull/152339) to update version numbers and sha256 for both `wp-cli` and `wp-cli-completion` - -### Updating the website - -- [ ] Verify is up-to-date - -- [ ] Update all version references on the homepage (and localized homepages). - - Can be mostly done by using search and replace for the version number and the blog post URL. - -- [ ] Update the [roadmap](https://make.wordpress.org/cli/handbook/roadmap/) to mention the current stable version - -- [ ] Tag a release of the website - -### Announcing - -- [ ] Publish the blog post - -- [ ] Announce release on the [WP-CLI Twitter account](https://twitter.com/wpcli) - -- [ ] Optional: Announce using the `/announce` slash command in the [`#cli`](https://wordpress.slack.com/messages/C02RP4T41) Slack room. - - This pings a lot of people, so it's not always desired. Plus, the blog post will pop up on Slack anyway. - -### Bumping WP-CLI version again - -- [ ] Bump [VERSION](https://github.com/wp-cli/wp-cli/blob/master/VERSION) in [`wp-cli/wp-cli`](https://github.com/wp-cli/wp-cli) again. - - For instance, if the release version was `2.8.0`, the version should be bumped to `2.9.0-alpha`. - - Doing so ensures `wp cli update --nightly` works as expected. - -- [ ] Change the version constraint on `"wp-cli/wp-cli"` in `wp-cli/wp-cli-bundle`'s [`composer.json`](https://github.com/wp-cli/wp-cli-bundle/blob/master/composer.json) file back to `"dev-main"`. - - ``` - composer require wp-cli/wp-cli:dev-main - ``` - -- [ ] Adapt the branch alias in `wp-cli/wp-cli`'s [`composer.json`](https://github.com/wp-cli/wp-cli/blob/master/composer.json) file to match the new alpha version. diff --git a/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md b/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md deleted file mode 100644 index e86ee350c..000000000 --- a/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -name: "\U0001F527 Patch Release Checklist" -about: "\U0001F512 Maintainers only: create a checklist for a patch release process" -title: 'Release checklist for v2.x.x' -labels: 'i: scope:distribution' -assignees: 'schlessera' - ---- -# Patch Release Checklist - v2.x.x - -### Preparation - -- [ ] Write release post on the [Make.org CLI blog](https://make.wordpress.org/cli/wp-admin/post-new.php) -- [ ] Regenerate command and internal API docs - - Command and internal API docs need to be regenerated before every major release, because they're meant to correlate with the release. - - ``` - git clone git@github.com:wp-cli/handbook.git - cd handbook - wp handbook gen-all - ``` - -- [ ] Verify results of [automated test suite](https://github.com/wp-cli/automated-tests) - -### Updating WP-CLI - -- [ ] Create a new release branch from the last tagged patch release - - ``` - $ git checkout v1.4.0 - Note: checking out 'v1.4.0' - You are in 'detached HEAD' state. You can look around, make experimental - changes and commit them, and you can discard any commits you make in this - state without impacting any branches by performing another checkout. - $ git checkout -b release-1-4-1 - Switched to a new branch 'release-1-4-1' - ``` - -- [ ] Cherry-pick existing commits and package versions to the new release branch. - - Because patch releases should just be used for bug fixes, you should first fix the bug on master, and then cherry-pick the fix to the release branch. It's up to your discretion as to whether you cherry-pick the commits directly to the release branch *or* create a feature branch and pull request against the release branch. - - If the bug existed in a package, you'll need to create a point release above the last bundled version for the package and update `composer.lock` to load that point release. - -- [ ] Ensure that the contents of [VERSION](https://github.com/wp-cli/wp-cli/blob/master/VERSION) in `wp-cli/wp-cli` are changed to latest. - -- [ ] Update the WP-CLI version mention in `wp-cli/wp-cli`'s `README.md` ([ref](https://github.com/wp-cli/wp-cli/issues/3647)). - -- [ ] Lock `php-cli-tools` version (if needed) - - `php-cli-tools` is sometimes set to `dev-master` during the development cycle. During the WP-CLI release process, `composer.json` should be locked to a specific version. `php-cli-tools` may need a new version tagged as well. - -- [ ] Lock the framework version in the ([bundle repository](https://github.com/wp-cli/wp-cli-bundle/)) - - The version constraint of the `wp-cli/wp-cli` framework requirement is usually set to `"dev-master"`. Set it to the stable tagged release that represents the version to be published. - - As an example, if releasing version 2.1.0 of WP-CLI, the `wp-cli/wp-cli-bundle` should require `"wp-cli/wp-cli": "^2.1.0"`. - -### Updating the contributor list - -- [ ] Fetch the list of contributors (from within the [`wp-cli/wp-cli-dev`](https://githubcom/wp-cli/wp-cli-dev/) project repo) - - From within the `wp-cli/wp-cli-dev` project repo, use `wp maintenance contrib-list` to generate a list of release contributors: - - ``` - GITHUB_TOKEN= wp maintenance contrib-list --format=markdown - ``` - - This script identifies pull request creators from `wp-cli/wp-cli-bundle`, `wp-cli/wp-cli`, `wp-cli/handbook`, and all bundled WP-CLI commands (e.g. `wp-cli/*-command`). - - For `wp-cli/wp-cli-bundle`, `wp-cli/wp-cli` and `wp-cli/handbook`, the script uses the currently open release milestone. - - For all bundled WP-CLI commands, the script uses all closed milestones since the last WP-CLI release (as identified by the version present in the `composer.lock` file). If a command was newly bundled since last release, contributors to that command will need to be manually added to the list. - - The script will also produce a total contributor and pull request count you can use in the release post. - -### Updating the Phar build - -- [ ] Create a PR from the `release-x-x-x` branch in `wp-cli/wp-cli-bundle` and merge it. This will trigger the `wp-cli-release.*` builds. - -- [ ] Create a git tag and push it. - -- [ ] Create a stable [Phar build](https://github.com/wp-cli/builds/tree/gh-pages/phar): - - ``` - cd wp-cli/builds/phar - cp wp-cli-release.phar wp-cli.phar - cp wp-cli-release.manifest.json wp-cli.manifest.json - md5 -q wp-cli.phar > wp-cli.phar.md5 - shasum -a 512 wp-cli.phar | cut -d ' ' -f 1 > wp-cli.phar.sha512 - ``` - -- [ ] Sign the release with GPG (see ): - - ``` - gpg --output wp-cli.phar.gpg --default-key releases@wp-cli.org --sign wp-cli.phar - gpg --output wp-cli.phar.asc --default-key releases@wp-cli.org --detach-sig --armor wp-cli.phar - ``` - - Note: The GPG key for `releases@wp-cli.org` has to be shared amongst maintainers. - -- [ ] Perform one last sanity check on the Phar by ensuring it displays its information - - ``` - php wp-cli.phar --info - ``` - -- [ ] Commit the Phar and its hashes to the builds repo - - ``` - git status - git add . - git commit -m "Update stable to v2.x.x" - ``` - -- [ ] Create a release on GitHub: . Make sure to upload the Phar from the builds directory. - - ``` - cp wp-cli.phar wp-cli-2.x.x.phar - cp wp-cli.phar.gpg wp-cli-2.x.x.phar.gpg - cp wp-cli.phar.asc wp-cli-2.x.x.phar.asc - cp wp-cli.phar.md5 wp-cli-2.x.x.phar.md5 - cp wp-cli.phar.sha512 wp-cli-2.x.x.phar.sha512 - cp wp-cli.manifest.json wp-cli-2.x.x.manifest.json - ``` - -- [ ] Verify Phar release artifact - - ``` - $ wp cli update - You are currently using WP-CLI version 2.12.0-alpha-d2bfea9. Would you like to update to 2.12.1? [y/n] y - Downloading from https://github.com/wp-cli/wp-cli/releases/download/v2.12.1/wp-cli-2.12.1.phar... - sha512 hash verified: fe19025cc113142492a3ca68dd93d20ba4164e5ecb3c0a0d86a9db7e06b917201120763fa2b8256addeaa9cb745b2b8bef8e8d74a697230e30ef681f13e09186 - New version works. Proceeding to replace. - Success: Updated WP-CLI to 2.12.1. - $ wp cli version - WP-CLI 2.12.1 - $wp eval 'echo \WP_CLI\Utils\http_request( "GET", "https://api.wordpress.org/core/version-check/1.6/" )->body;' --skip-wordpress - - ``` - -### Updating the Debian and RPM builds - -- [ ] Trigger Travis CI build on [wp-cli/deb-build](https://github.com/wp-cli/deb-build) -- [ ] Trigger Travis CI build on [wp-cli/rpm-build](https://github.com/wp-cli/rpm-build) - - The two builds shouldn't be triggered at the same time, as one of them will then fail to push its build artifact due to the remote not being in the same state anymore. - - Due to aggressive caching by the GitHub servers, the scripts might pull in cached version of the previous release instead of the new one. This seems to resolve automatically in a period of 24 hours. - -### Updating the Homebrew formula (should happen automatically) - -- [ ] Update the url and sha256 here: https://github.com/Homebrew/homebrew-core/blob/master/Formula/wp-cli.rb#L4-L5 - - The easiest way to do so is by using the following command: - - ``` - brew bump-formula-pr --strict wp-cli --url=https://github.com/wp-cli/wp-cli/releases/download/v2.x.x/wp-cli-2.x.x.phar --sha256=$(wget -qO- https://github.com/wp-cli/wp-cli/releases/download/v2.x.x/wp-cli-2.x.x.phar - | sha256sum | cut -d " " -f 1) - ``` - -### Updating the website - -- [ ] Verify is up-to-date - -- [ ] Update the [roadmap](https://make.wordpress.org/cli/handbook/roadmap/) - -- [ ] Update all version references on the homepage (and localized homepages). - -- [ ] Tag a release of the website - -### Announcing - -- [ ] Announce release on the [WP-CLI Twitter account](https://twitter.com/wpcli) -- [ ] Announce using the `/announce` slash command in the [`#cli`](https://wordpress.slack.com/messages/C02RP4T41) Slack room. - -### Bumping WP-CLI version again - -- [ ] Bump [VERSION](https://github.com/wp-cli/wp-cli/blob/master/VERSION) in [`wp-cli/wp-cli`](https://github.com/wp-cli/wp-cli) again. - - For instance, if the release version was `2.8.0`, the version should be bumped to `2.9.0-alpha`. - - Doing so ensures `wp cli update --nightly` works as expected. - -- [ ] Change the version constraint on `"wp-cli/wp-cli"` in `wp-cli/wp-cli-bundle`'s [`composer.json`](https://github.com/wp-cli/wp-cli-bundle/blob/main/composer.json) file back to `"dev-main"`. - - ``` - composer require wp-cli/wp-cli:dev-main - ``` diff --git a/.github/SECURITY.md b/.github/SECURITY.md deleted file mode 100644 index 70c780fcb..000000000 --- a/.github/SECURITY.md +++ /dev/null @@ -1,8 +0,0 @@ -# Reporting Security Issues - -Thanks for wanting to help keep WP-CLI and the larger WordPress community secure! - -The WP-CLI team and WordPress community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. - -To report a security issue, please visit the [WordPress HackerOne program](https://hackerone.com/wordpress). - diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index d6c7b8b04..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -updates: - - package-ecosystem: composer - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - labels: - - scope:distribution - - package-ecosystem: github-actions - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - labels: - - scope:distribution - diff --git a/.github/workflows/check-branch-alias.yml b/.github/workflows/check-branch-alias.yml deleted file mode 100644 index 78da63710..000000000 --- a/.github/workflows/check-branch-alias.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Check Branch Alias - -on: - release: - types: [released] - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - check-branch-alias: - uses: wp-cli/.github/.github/workflows/reusable-check-branch-alias.yml@main diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml deleted file mode 100644 index e9fe57761..000000000 --- a/.github/workflows/code-quality.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Code Quality Checks - -on: - pull_request: - push: - branches: - - main - - master - schedule: - - cron: '17 2 * * *' # Run every day on a seemly random time. - -jobs: - code-quality: - uses: wp-cli/.github/.github/workflows/reusable-code-quality.yml@main diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml deleted file mode 100644 index 844ffe251..000000000 --- a/.github/workflows/copilot-setup-steps.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: "Copilot Setup Steps" - -on: - workflow_dispatch: - push: - paths: - - .github/workflows/copilot-setup-steps.yml - pull_request: - paths: - - .github/workflows/copilot-setup-steps.yml - -permissions: - contents: read - -jobs: - copilot-setup-steps: - name: Setup environment - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - - name: Check existence of composer.json file - id: check_composer_file - run: echo "files_exists=$(test -f composer.json && echo true || echo false)" >> "$GITHUB_OUTPUT" - - - name: Set up PHP environment - if: steps.check_composer_file.outputs.files_exists == 'true' - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 - with: - php-version: 'latest' - ini-values: zend.assertions=1, error_reporting=-1, display_errors=On - coverage: 'none' - tools: composer,cs2pr - env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Composer dependencies & cache dependencies - if: steps.check_composer_file.outputs.files_exists == 'true' - uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 - env: - COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }} diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml deleted file mode 100644 index 68334703a..000000000 --- a/.github/workflows/issue-triage.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Issue and PR Triage - -'on': - issues: - types: [opened] - pull_request_target: - types: [opened] - workflow_dispatch: - inputs: - issue_number: - description: 'Issue/PR number to triage (leave empty to process all)' - required: false - type: string - -permissions: - issues: write - pull-requests: write - actions: write - contents: read - models: read - -jobs: - issue-triage: - uses: wp-cli/.github/.github/workflows/reusable-issue-triage.yml@main - with: - issue_number: >- - ${{ - (github.event_name == 'workflow_dispatch' && inputs.issue_number) || - (github.event_name == 'pull_request_target' && github.event.pull_request.number) || - (github.event_name == 'issues' && github.event.issue.number) || - '' - }} diff --git a/.github/workflows/manage-labels.yml b/.github/workflows/manage-labels.yml deleted file mode 100644 index 45711bded..000000000 --- a/.github/workflows/manage-labels.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Manage Labels - -'on': - workflow_dispatch: - push: - branches: - - main - - master - paths: - - 'composer.json' - -permissions: - issues: write - contents: read - -jobs: - manage-labels: - uses: wp-cli/.github/.github/workflows/reusable-manage-labels.yml@main diff --git a/.github/workflows/regenerate-readme.yml b/.github/workflows/regenerate-readme.yml deleted file mode 100644 index 6198d6308..000000000 --- a/.github/workflows/regenerate-readme.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Regenerate README file - -on: - workflow_dispatch: - push: - branches: - - main - - master - paths-ignore: - - "features/**" - - "README.md" - -permissions: - contents: write - pull-requests: write - -jobs: - regenerate-readme: - uses: wp-cli/.github/.github/workflows/reusable-regenerate-readme.yml@main diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml deleted file mode 100644 index bf67592d8..000000000 --- a/.github/workflows/testing.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Testing - -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - - master - schedule: - - cron: '17 1 * * *' # Run every day on a seemly random time. - -jobs: - test: - uses: wp-cli/.github/.github/workflows/reusable-testing.yml@main diff --git a/.github/workflows/trigger-handbook-regeneration.yml b/.github/workflows/trigger-handbook-regeneration.yml deleted file mode 100644 index 43a91c234..000000000 --- a/.github/workflows/trigger-handbook-regeneration.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Trigger Handbook Regeneration - -on: - release: - types: [released] - workflow_dispatch: - -permissions: {} - -jobs: - trigger-handbook: - name: Trigger Handbook Regeneration - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'wp-cli' }} - steps: - - name: Trigger repository dispatch in wp-cli/handbook - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.ACTIONS_BOT }} - script: | - await github.rest.repos.createDispatchEvent({ - owner: 'wp-cli', - repo: 'handbook', - event_type: 'regenerate-handbook', - client_payload: {} - }); diff --git a/.github/workflows/update-requests.yml b/.github/workflows/update-requests.yml deleted file mode 100644 index c4fef0db6..000000000 --- a/.github/workflows/update-requests.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: Update Requests library - -on: - schedule: - - cron: '0 3 * * 1' # Run every Monday at 03:00 UTC. - workflow_dispatch: - -concurrency: - group: update-requests - cancel-in-progress: true -permissions: - contents: write - pull-requests: write - -jobs: - update-requests: - name: Check and update Requests library - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: Get the latest Requests release tag - id: latest_release - env: - GH_TOKEN: ${{ github.token }} - run: | - LATEST_TAG=$(gh api repos/WordPress/Requests/releases/latest --jq '.tag_name') - if [[ -z "${LATEST_TAG}" || "${LATEST_TAG}" == "null" ]]; then - echo "Failed to retrieve latest Requests release tag." >&2 - exit 1 - fi - echo "tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT" - - - name: Get the current Requests version from install script - id: current_version - run: | - CURRENT_TAG=$(grep -oP 'REQUESTS_TAG="\K[^"]+' utils/install-requests.sh) - if [[ -z "${CURRENT_TAG}" ]]; then - echo "Failed to determine current Requests version from utils/install-requests.sh." >&2 - exit 1 - fi - echo "tag=${CURRENT_TAG}" >> "$GITHUB_OUTPUT" - - - name: Update install script and bundle if versions differ - if: steps.latest_release.outputs.tag != steps.current_version.outputs.tag - env: - LATEST_TAG: ${{ steps.latest_release.outputs.tag }} - CURRENT_TAG: ${{ steps.current_version.outputs.tag }} - run: | - sed -i "s/REQUESTS_TAG=\"${CURRENT_TAG}\"/REQUESTS_TAG=\"${LATEST_TAG}\"/" utils/install-requests.sh - grep -q "REQUESTS_TAG=\"${LATEST_TAG}\"" utils/install-requests.sh || { echo "Failed to update REQUESTS_TAG in install script." >&2; exit 1; } - bash utils/install-requests.sh - - - name: Validate modified files - if: steps.latest_release.outputs.tag != steps.current_version.outputs.tag - run: | - mapfile -t changed_files < <(git diff --name-only) - - if [ "${#changed_files[@]}" -eq 0 ]; then - echo "No files changed by update; nothing to validate." - exit 0 - fi - - allowed_regex='^(utils/install-requests\.sh|bundle/rmccue/requests(/|$))' - disallowed=() - - for f in "${changed_files[@]}"; do - if ! [[ "$f" =~ $allowed_regex ]]; then - disallowed+=("$f") - fi - done - - if [ "${#disallowed[@]}" -ne 0 ]; then - echo "Error: Unexpected files were modified by utils/install-requests.sh:" - printf ' %s\n' "${disallowed[@]}" - exit 1 - fi - - echo "All modified files are within the allowed paths." - - name: Commit and Create Pull Request - if: steps.latest_release.outputs.tag != steps.current_version.outputs.tag - env: - GH_TOKEN: ${{ github.token }} - BRANCH_NAME: "update/requests-${{ steps.latest_release.outputs.tag }}" - PR_BODY: | - This automated PR updates the bundled [Requests](https://github.com/WordPress/Requests) library from `${{ steps.current_version.outputs.tag }}` to `${{ steps.latest_release.outputs.tag }}`. - - Please review the [Requests changelog](https://github.com/WordPress/Requests/releases/tag/${{ steps.latest_release.outputs.tag }}) before merging. - run: | - if [ -n "$(git status --porcelain)" ]; then - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout -b "$BRANCH_NAME" - git add utils/install-requests.sh bundle/rmccue/requests/ - git commit -m "Update bundled Requests library to ${{ steps.latest_release.outputs.tag }}" - git push -f origin "$BRANCH_NAME" - - PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number // empty') - if [ -z "$PR_NUMBER" ]; then - gh pr create \ - --title "Update bundled Requests library to ${{ steps.latest_release.outputs.tag }}" \ - --body "$PR_BODY" \ - --label "Requests" \ - --base "${{ github.event.repository.default_branch }}" \ - --head "$BRANCH_NAME" - else - gh pr edit "$PR_NUMBER" \ - --title "Update bundled Requests library to ${{ steps.latest_release.outputs.tag }}" \ - --body "$PR_BODY" \ - --add-label "Requests" \ - --base "${{ github.event.repository.default_branch }}" - fi - fi - - diff --git a/.github/workflows/welcome-new-contributors.yml b/.github/workflows/welcome-new-contributors.yml deleted file mode 100644 index bc01490b3..000000000 --- a/.github/workflows/welcome-new-contributors.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Welcome New Contributors - -on: - pull_request_target: - types: [opened] - branches: - - main - - master - -permissions: - pull-requests: write - -jobs: - welcome: - uses: wp-cli/.github/.github/workflows/reusable-welcome-new-contributors.yml@main diff --git a/.gitignore b/.gitignore index 5ad4029f1..1e0014bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,7 @@ PHAR_BUILD_VERSION /packages /vendor /*.phar +/phpunit.xml.dist +/codesniffer +/PHP_Codesniffer-VariableAnalysis .*.swp -*.log -phpunit.xml -phpcs.xml -.phpcs.xml -composer.lock -.phpunit.result.cache -.phpunit.cache -/build/logs diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..ad37613da --- /dev/null +++ b/.travis.yml @@ -0,0 +1,69 @@ +sudo: false +dist: trusty + +language: php +php: 7.1 + +env: + global: + - WP_CLI_BIN_DIR=/tmp/wp-cli-phar + +before_script: + - export PATH="$HOME/.composer/vendor/bin:$PATH" + - ./ci/prepare.sh + +jobs: + include: + - stage: sniff + script: ./ci/sniff.sh + env: BUILD=sniff + - stage: test + script: ./ci/test.sh + php: 7.2 + env: WP_VERSION=latest + - stage: test + script: ./ci/test.sh + php: 7.1 + env: WP_VERSION=latest + - stage: test + script: ./ci/test.sh + php: 7.0 + env: WP_VERSION=latest + - stage: test + script: ./ci/test.sh + php: 5.6 + env: WP_VERSION=latest + - stage: test + script: ./ci/test.sh + php: 5.6 + env: WP_VERSION=trunk + - stage: test + script: ./ci/test.sh + php: 5.6 + env: WP_VERSION=latest BUILD=git WP_CLI_BIN_DIR='' + - stage: test + script: ./ci/test.sh + php: 5.6 + env: WP_VERSION=3.7.11 + - stage: test + script: ./ci/test.sh + php: 5.3 + dist: precise + env: WP_VERSION=3.7.11 + - stage: deploy + env: DEPLOY_BRANCH=master + script: ./ci/deploy.sh + +cache: + directories: + - $HOME/.composer/cache + +branches: + only: + - master + - /^release-.+$/ + +notifications: + email: + on_success: never + on_failure: change diff --git a/.typos.toml b/.typos.toml deleted file mode 100644 index cf6e84bf9..000000000 --- a/.typos.toml +++ /dev/null @@ -1,14 +0,0 @@ -[default] -extend-ignore-re = [ - "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", - "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on", - "(#|//)\\s*spellchecker:ignore-next-line\\n.*" -] - -[files] -extend-exclude = [ - "bundle/*", - "php/WP_CLI/Inflector.php", - "tests/*.php", - "features/*.feature" -] diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 1ff84f6d1..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,121 +0,0 @@ -# Instructions - -This package is part of WP-CLI, the official command line interface for WordPress. For a detailed explanation of the project structure and development workflow, please refer to the main @README.md file. - -## Best Practices for Code Contributions - -When contributing to this package, please adhere to the following guidelines: - -* **Follow Existing Conventions:** Before writing any code, analyze the existing codebase in this package to understand the coding style, naming conventions, and architectural patterns. -* **Focus on the Package's Scope:** All changes should be relevant to the functionality of the package. -* **Write Tests:** All new features and bug fixes must be accompanied by acceptance tests using Behat. You can find the existing tests in the `features/` directory. There may be PHPUnit unit tests as well in the `tests/` directory. -* **Update Documentation:** If your changes affect the user-facing functionality, please update the relevant inline code documentation. - -### Building and running - -Before submitting any changes, it is crucial to validate them by running the full suite of static code analysis and tests. To run the full suite of checks, execute the following command: `composer test`. - -This single command ensures that your changes meet all the quality gates of the project. While you can run the individual steps separately, it is highly recommended to use this single command to ensure a comprehensive validation. - -### Useful Composer Commands - -The project uses Composer to manage dependencies and run scripts. The following commands are available: - -* `composer install`: Install dependencies. -* `composer test`: Run the full test suite, including linting, code style checks, static analysis, and unit/behavior tests. -* `composer lint`: Check for syntax errors. -* `composer phpcs`: Check for code style violations. -* `composer phpcbf`: Automatically fix code style violations. -* `composer phpstan`: Run static analysis. -* `composer phpunit`: Run unit tests. -* `composer behat`: Run behavior-driven tests. - -### Coding Style - -The project follows the `WP_CLI_CS` coding standard, which is enforced by PHP_CodeSniffer. The configuration can be found in `phpcs.xml.dist`. Before submitting any code, please run `composer phpcs` to check for violations and `composer phpcbf` to automatically fix them. - -## Documentation - -The `README.md` file might be generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). In that case, changes need to be made against the corresponding part of the codebase. - -### Inline Documentation - -Only write high-value comments if at all. Avoid talking to the user through comments. - -## Testing - -The project has a comprehensive test suite that includes unit tests, behavior-driven tests, and static analysis. - -* **Unit tests** are written with PHPUnit and can be found in the `tests/` directory. The configuration is in `phpunit.xml.dist`. -* **Behavior-driven tests** are written with Behat and can be found in the `features/` directory. The configuration is in `behat.yml`. -* **Static analysis** is performed with PHPStan. - -All tests are run on GitHub Actions for every pull request. - -When writing tests, aim to follow existing patterns. Key conventions include: - -* When adding tests, first examine existing tests to understand and conform to established conventions. -* For unit tests, extend the base `WP_CLI\Tests\TestCase` test class. -* For Behat tests, only WP-CLI commands installed in `composer.json` can be run. - -### Behat Steps - -WP-CLI makes use of a Behat-based testing framework and provides a set of custom step definitions to write feature tests. - -> **Note:** If you are expecting an error output in a test, you need to use `When I try ...` instead of `When I run ...` . - -#### Given - -* `Given an empty directory` - Creates an empty directory. -* `Given /^an? (empty|non-existent) ([^\s]+) directory$/` - Creates or deletes a specific directory. -* `Given an empty cache` - Clears the WP-CLI cache directory. -* `Given /^an? ([^\s]+) (file|cache file):$/` - Creates a file with the given contents. -* `Given /^"([^"]+)" replaced with "([^"]+)" in the ([^\s]+) file$/` - Search and replace a string in a file using regex. -* `Given /^that HTTP requests to (.*?) will respond with:$/` - Mock HTTP requests to a given URL. -* `Given WP files` - Download WordPress files without installing. -* `Given wp-config.php` - Create a wp-config.php file using `wp config create`. -* `Given a database` - Creates an empty database. -* `Given a WP install(ation)` - Installs WordPress. -* `Given a WP install(ation) in :subdir` - Installs WordPress in a given directory. -* `Given a WP install(ation) with Composer` - Installs WordPress with Composer. -* `Given a WP install(ation) with Composer and a custom vendor directory :vendor_directory` - Installs WordPress with Composer and a custom vendor directory. -* `Given /^a WP multisite (subdirectory|subdomain)?\s?(install|installation)$/` - Installs WordPress Multisite. -* `Given these installed and active plugins:` - Installs and activates one or more plugins. -* `Given a custom wp-content directory` - Configure a custom `wp-content` directory. -* `Given download:` - Download multiple files into the given destinations. -* `Given /^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/` - Store STDOUT or STDERR contents in a variable. -* `Given /^a new Phar with (?:the same version|version "([^"]+)")$/` - Build a new WP-CLI Phar file with a given version. -* `Given /^a downloaded Phar with (?:the same version|version "([^"]+)")$/` - Download a specific WP-CLI Phar version from GitHub. -* `Given /^save the (.+) file ([\'].+[^\'])? as \{(\w+)\}$/` - Stores the contents of the given file in a variable. -* `Given a misconfigured WP_CONTENT_DIR constant directory` - Modify wp-config.php to set `WP_CONTENT_DIR` to an empty string. -* `Given a dependency on current wp-cli` - Add `wp-cli/wp-cli` as a Composer dependency. -* `Given a PHP built-in web server` - Start a PHP built-in web server in the current directory. -* `Given a PHP built-in web server to serve :subdir` - Start a PHP built-in web server in the given subdirectory. - -#### When - -* ``When /^I launch in the background `([^`]+)`$/`` - Launch a given command in the background. -* ``When /^I (run|try) `([^`]+)`$/`` - Run or try a given command. -* ``When /^I (run|try) `([^`]+)` from '([^\s]+)'$/`` - Run or try a given command in a subdirectory. -* `When /^I (run|try) the previous command again$/` - Run or try the previous command again. - -#### Then - -* `Then /^the return code should( not)? be (\d+)$/` - Expect a specific exit code of the previous command. -* `Then /^(STDOUT|STDERR) should( strictly)? (be|contain|not contain):$/` - Check the contents of STDOUT or STDERR. -* `Then /^(STDOUT|STDERR) should be a number$/` - Expect STDOUT or STDERR to be a numeric value. -* `Then /^(STDOUT|STDERR) should not be a number$/` - Expect STDOUT or STDERR to not be a numeric value. -* `Then /^STDOUT should be a table containing rows:$/` - Expect STDOUT to be a table containing the given rows. -* `Then /^STDOUT should end with a table containing rows:$/` - Expect STDOUT to end with a table containing the given rows. -* `Then /^STDOUT should be JSON containing:$/` - Expect valid JSON output in STDOUT. -* `Then /^STDOUT should be a JSON array containing:$/` - Expect valid JSON array output in STDOUT. -* `Then /^STDOUT should be CSV containing:$/` - Expect STDOUT to be CSV containing certain values. -* `Then /^STDOUT should be YAML containing:$/` - Expect STDOUT to be YAML containing certain content. -* `Then /^(STDOUT|STDERR) should be empty$/` - Expect STDOUT or STDERR to be empty. -* `Then /^(STDOUT|STDERR) should not be empty$/` - Expect STDOUT or STDERR not to be empty. -* `Then /^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|<>) ([+\w.{}-]+)$/` - Expect STDOUT or STDERR to be a version string comparing to the given version. -* `Then /^the (.+) (file|directory) should( strictly)? (exist|not exist|be:|contain:|not contain):$/` - Expect a certain file or directory to (not) exist or (not) contain certain contents. -* `Then /^the contents of the (.+) file should( not)? match (((\/.*\/)|(#.#))([a-z]+)?)$/` - Match file contents against a regex. -* `Then /^(STDOUT|STDERR) should( not)? match (((\/.*\/)|(#.#))([a-z]+)?)$/` - Match STDOUT or STDERR against a regex. -* `Then /^an email should (be sent|not be sent)$/` - Expect an email to be sent (or not). -* `Then the HTTP status code should be :code` - Expect the HTTP status code for visiting `http://localhost:8080`. diff --git a/LICENSE b/LICENSE index 9fb2105e0..e56e9341d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (C) 2011-2024 WP-CLI Development Group (https://github.com/wp-cli/wp-cli/contributors) +Copyright (C) 2011-2017 WP-CLI Development Group (https://github.com/wp-cli/wp-cli/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 diff --git a/README.md b/README.md index 36f6559c9..ccb14cd92 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,15 @@ WP-CLI ====== -[WP-CLI](https://wp-cli.org/) is the command-line interface for [WordPress](https://wordpress.org/). You can update plugins, configure multisite installations, and much more, without using a web browser. +[WP-CLI](https://wp-cli.org/) is the command-line interface for [WordPress](https://wordpress.org/). You can update plugins, configure multisite installs and much more, without using a web browser. -Ongoing maintenance is made possible by: +Ongoing maintenance is made possible by: - - - - - - + -The current stable release is [version 2.12.0](https://make.wordpress.org/cli/2025/05/07/wp-cli-v2-12-0-release-notes/). For announcements, follow [@wpcli on Twitter](https://twitter.com/wpcli) or [sign up for email updates](https://make.wordpress.org/cli/subscribe/). [Check out the roadmap](https://make.wordpress.org/cli/handbook/roadmap/) for an overview of what's planned for upcoming releases. +The current stable release is [version 1.4.1](https://make.wordpress.org/cli/2017/11/13/version-1-4-1-released/). For announcements, follow [@wpcli on Twitter](https://twitter.com/wpcli) or [sign up for email updates](https://make.wordpress.org/cli/subscribe/). [Check out the roadmap](https://make.wordpress.org/cli/handbook/roadmap/) for an overview of what's planned for upcoming releases. -[![Testing](https://github.com/wp-cli/wp-cli/actions/workflows/testing.yml/badge.svg)](https://github.com/wp-cli/wp-cli/actions/workflows/testing.yml) [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/wp-cli/wp-cli.svg)](https://isitmaintained.com/project/wp-cli/wp-cli "Average time to resolve an issue") [![Percentage of issues still open](https://isitmaintained.com/badge/open/wp-cli/wp-cli.svg)](https://isitmaintained.com/project/wp-cli/wp-cli "Percentage of issues still open") +[![Build Status](https://travis-ci.org/wp-cli/wp-cli.svg?branch=master)](https://travis-ci.org/wp-cli/wp-cli) [![Dependency Status](https://gemnasium.com/badges/github.com/wp-cli/wp-cli.svg)](https://gemnasium.com/github.com/wp-cli/wp-cli) [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/wp-cli/wp-cli.svg)](https://isitmaintained.com/project/wp-cli/wp-cli "Average time to resolve an issue") [![Percentage of issues still open](https://isitmaintained.com/badge/open/wp-cli/wp-cli.svg)](https://isitmaintained.com/project/wp-cli/wp-cli "Percentage of issues still open") Quick links: [Using](#using) | [Installing](#installing) | [Support](#support) | [Extending](#extending) | [Contributing](#contributing) | [Credits](#credits) @@ -25,7 +20,7 @@ WP-CLI provides a command-line interface for many actions you might perform in t ```bash $ wp plugin install user-switching --activate Installing User Switching (1.0.9) -Downloading installation package from https://downloads.wordpress.org/plugin/user-switching.1.0.9.zip... +Downloading install package from https://downloads.wordpress.org/plugin/user-switching.1.0.9.zip... Unpacking the package... Installing the plugin... Plugin installed successfully. @@ -47,15 +42,15 @@ Already feel comfortable with the basics? Jump into the [complete list of comman ## Installing -Downloading the Phar file is the recommended installation method for most users. See the documentation for [alternative installation methods](https://make.wordpress.org/cli/handbook/installing/) ([Composer](https://make.wordpress.org/cli/handbook/installing/#installing-via-composer), [Homebrew](https://make.wordpress.org/cli/handbook/installing/#installing-via-homebrew), [Docker](https://make.wordpress.org/cli/handbook/installing/#installing-via-docker)). +Downloading the Phar file is our recommended installation method for most users. Should you need, see also our documentation on [alternative installation methods](https://make.wordpress.org/cli/handbook/installing/). Before installing WP-CLI, please make sure your environment meets the minimum requirements: - UNIX-like environment (OS X, Linux, FreeBSD, Cygwin); limited support in Windows environment -- PHP 7.2.24 or later -- WordPress 4.9 or later. Versions older than the latest WordPress release may have degraded functionality +- PHP 5.3.29 or later +- WordPress 3.7 or later. Versions older than the latest WordPress release may have degraded functionality -Once you've verified requirements, download the [wp-cli.phar](https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar) file using `wget` or `curl`: +Once you've verified requirements, download the [wp-cli.phar](https://raw.github.com/wp-cli/builds/gh-pages/phar/wp-cli.phar) file using `wget` or `curl`: ```bash curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar @@ -78,21 +73,14 @@ If WP-CLI was installed successfully, you should see something like this when yo ```bash $ wp --info -OS: Linux 5.10.60.1-microsoft-standard-WSL2 #1 SMP Wed Aug 25 23:20:18 UTC 2021 x86_64 -Shell: /usr/bin/zsh -PHP binary: /usr/bin/php8.1 -PHP version: 8.1.0 -php.ini used: /etc/php/8.1/cli/php.ini -MySQL binary: /usr/bin/mysql -MySQL version: mysql Ver 8.0.27-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) -SQL modes: -WP-CLI root dir: /home/wp-cli/ -WP-CLI vendor dir: /home/wp-cli/vendor -WP_CLI phar path: +PHP binary: /usr/bin/php5 +PHP version: 5.5.9-1ubuntu4.14 +php.ini used: /etc/php5/cli/php.ini +WP-CLI root dir: /home/wp-cli/.wp-cli WP-CLI packages dir: /home/wp-cli/.wp-cli/packages/ -WP-CLI global config: -WP-CLI project config: /home/wp-cli/wp-cli.yml -WP-CLI version: 2.12.0 +WP-CLI global config: /home/wp-cli/.wp-cli/config.yml +WP-CLI project config: +WP-CLI version: 1.4.1 ``` ### Updating @@ -105,7 +93,7 @@ Want to live life on the edge? Run `wp cli update --nightly` to use the latest n ### Tab completions -WP-CLI also comes with a tab completion script for Bash and ZSH. Just download [wp-completion.bash](https://raw.githubusercontent.com/wp-cli/wp-cli/v2.6.0/utils/wp-completion.bash) and source it from `~/.bash_profile`: +WP-CLI also comes with a tab completion script for Bash and ZSH. Just download [wp-completion.bash](https://raw.githubusercontent.com/wp-cli/wp-cli/master/utils/wp-completion.bash) and source it from `~/.bash_profile`: ```bash source /FULL/PATH/TO/wp-completion.bash @@ -140,7 +128,7 @@ If you didn't find an answer in one of the venues above, you can: GitHub issues are meant for tracking enhancements to and bugs of existing commands, not general support. Before submitting a bug report, please [review our best practices](https://make.wordpress.org/cli/handbook/bug-reports/) to help ensure your issue is addressed in a timely manner. -Please do not ask support questions on Twitter. Twitter isn't an acceptable venue for support because: 1) it's hard to hold conversations in under 280 characters, and 2) Twitter isn't a place where someone with your same question can search for an answer in a prior conversation. +Please do not ask support questions on Twitter. Twitter isn't an acceptable venue for support because: 1) it's hard to hold conversations in under 140 characters, and 2) Twitter isn't a place where someone with your same question can search for an answer in a prior conversation. Remember, libre != gratis; the open source license grants you the freedom to use and modify, but not commitments of other people's time. Please be respectful, and set your expectations accordingly. @@ -190,7 +178,7 @@ Read through our [contributing guidelines in the handbook](https://make.wordpres ## Leadership -WP-CLI has one project maintainer: [schlessera](http://github.com/schlessera). +WP-CLI has two project maintainers: [danielbachhuber](https://github.com/danielbachhuber) and [schlessera](http://github.com/schlessera). On occasion, we [grant write access to contributors](https://make.wordpress.org/cli/handbook/committers-credo/) who have demonstrated, over a period of time, that they are capable and invested in moving the project forward. diff --git a/VERSION b/VERSION index d97d65805..1433486b4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.13.0-alpha +1.5.0-alpha diff --git a/behat.yml b/behat.yml deleted file mode 100644 index d6ee86224..000000000 --- a/behat.yml +++ /dev/null @@ -1,7 +0,0 @@ -default: - suites: - default: - contexts: - - WP_CLI\Tests\Context\FeatureContext - paths: - - features diff --git a/bin/wp b/bin/wp index d741e64d9..053e24227 100755 --- a/bin/wp +++ b/bin/wp @@ -2,9 +2,9 @@ # # This wrapper script has been adapted from the equivalent drush wrapper # and 99.9% of all credit should go to the authors of that project: -# https://www.drupal.org/project/drush +# http://drupal.org/project/drush # And 0.09% to the author of this project: -# https://github.com/bROthersRockers/wpadmin/blob/master/wpadmin.php +# https://github.com/88mph/wpadmin/blob/master/wpadmin.php # Get the absolute path of this executable ORIGDIR="$(pwd)" @@ -27,8 +27,8 @@ if [ ! -z "$WP_CLI_PHP" ] ; then php="$WP_CLI_PHP" else # Default to using the php that we find on the PATH. - # Note that we need the full path to php here for Dreamhost, which behaves oddly. See https://www.drupal.org/node/662926 - php="$(command -v php)" + # Note that we need the full path to php here for Dreamhost, which behaves oddly. See http://drupal.org/node/662926 + php="`which php`" fi # Build the path to the root PHP file diff --git a/bin/wp.bat b/bin/wp.bat index 87c1e5a19..59f953440 100755 --- a/bin/wp.bat +++ b/bin/wp.bat @@ -1,2 +1,2 @@ @ECHO OFF -php %WP_CLI_PHP_ARGS% "%~dp0../php/boot-fs.php" %* \ No newline at end of file +php "%~dp0../php/boot-fs.php" %* \ No newline at end of file diff --git a/bundle/rmccue/requests/CHANGELOG.md b/bundle/rmccue/requests/CHANGELOG.md deleted file mode 100644 index 01b274ce8..000000000 --- a/bundle/rmccue/requests/CHANGELOG.md +++ /dev/null @@ -1,1166 +0,0 @@ -Changelog -========= - -2.0.18 ------- - -### Overview of changes -- Update bundled certificates as of 2026-02-11. [#1039] -- General housekeeping. - -[#1039]: https://github.com/WordPress/Requests/pull/1039 - -2.0.17 ------- - -### Overview of changes -- Update bundled certificates as of 2025-12-02. [#1000] -- General housekeeping. - -[#1000]: https://github.com/WordPress/Requests/pull/1000 - -2.0.16 ------- - -### Overview of changes -- Update bundled certificates as of 2025-11-04. [#954] -- Fixed: PHP 8.5 deprecation notices for `Reflection*::setAccessible()` [#940] -- Fixed: PHP 8.5 deprecation notices for `curl_close()` [#947] Props [@TobiasBg][gh-TobiasBg] -- Fixed: PHP 8.5 deprecation notices `Using null as an array offset` [#956] -- Fixed: Disallow `FilteredIterator` to accept objects (PHP 8.5 deprecation). [#968] - Note: This is technically a breaking change as this was documented behaviour. However, `FilteredIterator` is an internal class and the only detected use of this behavior was in a test. -- Fixed: tests for expired and revoked SSL certificates. [#967] -- Composer: remove `roave/security-advisories` (no longer needed with Composer 2.9+). [#961] -- README: corrected Python Requests library URL. [#944] Props [@pmbaldha][gh-pmbaldha] -- General housekeeping. - -[#940]: https://github.com/WordPress/Requests/pull/940 -[#944]: https://github.com/WordPress/Requests/pull/944 -[#947]: https://github.com/WordPress/Requests/pull/947 -[#954]: https://github.com/WordPress/Requests/pull/954 -[#956]: https://github.com/WordPress/Requests/pull/956 -[#961]: https://github.com/WordPress/Requests/pull/961 -[#967]: https://github.com/WordPress/Requests/pull/967 -[#968]: https://github.com/WordPress/Requests/pull/968 - -2.0.15 ------- - -### Overview of changes -- Update bundled certificates as of 2024-12-31. [#919] -- General housekeeping. - -[#919]: https://github.com/WordPress/Requests/pull/919 - -2.0.14 ------- - -### Overview of changes -- Update bundled certificates as of 2024-11-26. [#910] -- Confirmed compatibility with PHP 8.4. - No new changes were needed, so Request 2.0.11 and higher can be considered compatible with PHP 8.4. -- Various other general housekeeping. - -[#910]: https://github.com/WordPress/Requests/pull/910 - -2.0.13 ------- - -### Overview of changes -- Update bundled certificates as of 2024-09-24. [#900] -- Various minor documentation improvements and other general housekeeping. - -[#900]: https://github.com/WordPress/Requests/pull/900 - -2.0.12 ------- - -### Overview of changes -- Update bundled certificates as of 2024-07-02. [#877] - -[#877]: https://github.com/WordPress/Requests/pull/877 - -2.0.11 ------- - -### Overview of changes -- Update bundled certificates as of 2024-03-11. [#864] -- Fixed: PHP 8.4 deprecation of the two parameter signature of `stream_context_set_option()`. [#822] Props [@jrfnl][gh-jrfnl] -- Fixed: PHP 8.4 deprecation of implicitly nullable parameter. [#865] Props [@Ayesh][gh-ayesh], [@jrfnl][gh-jrfnl] - Note: this fix constitutes an, albeit small, breaking change to the signature of the `Cookie::parse_from_headers()` method. - Classes which extend the `Cookie` class and overload the `parse_from_headers()` method should be updated for the new method signature. - Additionally, if code calling the `Cookie::parse_from_headers()` method would be wrapped in a `try - catch` to catch a potential PHP `TypeError` (PHP 7.0+) or `Exception` (PHP < 7.0) for when invalid data was passed as the `$origin` parameter, this code will need to be updated to now also catch a potential `WpOrg\Requests\Exception\InvalidArgumentException`. - As due diligence could not find any classes which would be affected by this BC-break, we have deemed it acceptable to include this fix in the 2.0.11 release. - -[#822]: https://github.com/WordPress/Requests/pull/822 -[#864]: https://github.com/WordPress/Requests/pull/864 -[#865]: https://github.com/WordPress/Requests/pull/865 - -2.0.10 ------- - -### Overview of changes -- Update bundled certificates as of 2023-12-04. [#850] - -[#850]: https://github.com/WordPress/Requests/pull/850 - -2.0.9 ------ - -### Overview of changes -- Hotfix: Rollback changes from PR [#657]. [#839] Props [@tomsommer][gh-tomsommer] & [@laszlof][gh-laszlof] - -[#839]: https://github.com/WordPress/Requests/pull/839 - -2.0.8 ------ - -### Overview of changes -- Update bundled certificates as of 2023-08-22. [#823] -- Fixed: only force close cURL connection when needed (cURL < 7.22). [#656], [#657] Props [@mircobabini][gh-mircobabini] -- Composer: updated list of suggested PHP extensions to enable. [#821] -- README: add information about the PSR-7/PSR-18 wrapper for Requests. [#827] - -[#656]: https://github.com/WordPress/Requests/pull/656 -[#657]: https://github.com/WordPress/Requests/pull/657 -[#821]: https://github.com/WordPress/Requests/pull/821 -[#823]: https://github.com/WordPress/Requests/pull/823 -[#827]: https://github.com/WordPress/Requests/pull/827 - -2.0.7 ------ - -### Overview of changes -- Update bundled certificates as of 2023-05-30. [#809] - -[#809]: https://github.com/WordPress/Requests/pull/809 - -2.0.6 ------ - -### Overview of changes -- Update bundled certificates as of 2023-01-10. [#791] -- Fix typo in deprecation notice. [#785] Props [@costdev][gh-costdev] -- Minor internal improvements for passing the correct type to function calls. [#779] -- Confirmed compatibility with PHP 8.2. - No changes were needed, so Request 2.0.1 and higher can be considered compatible with PHP 8.2. -- Various documentation improvements and other general housekeeping. - -[#779]: https://github.com/WordPress/Requests/pull/779 -[#785]: https://github.com/WordPress/Requests/pull/785 -[#791]: https://github.com/WordPress/Requests/pull/791 - -2.0.5 ------ - -### Overview of changes -- Update bundled certificates as of 2022-10-11. [#769] - -[#769]: https://github.com/WordPress/Requests/pull/769 - -2.0.4 ------ - -### Overview of changes -- Update bundled certificates as of 2022-07-19. [#763] - -[#763]: https://github.com/WordPress/Requests/pull/763 - -2.0.3 ------ - -### Overview of changes -- Update bundled certificates as of 2022-04-26. [#731] - -[#731]: https://github.com/WordPress/Requests/pull/731 - -2.0.2 ------ - -### Overview of changes -- Update bundled certificates as of 2022-03-18. [#697] - -[#697]: https://github.com/WordPress/Requests/pull/697 - -2.0.1 ------ - -### Overview of changes -- Update bundled certificates as of 2022-02-01. [#670] -- Bug fix: Hook priority should be respected. [#452], [#647] -- Docs: the Hook documentation has been updated to reflect the current available hooks. [#646] -- General housekeeping. [#635], [#649], [#650], [#653], [#655], [#658], [#660], [#661], [#662], [#669], [#671], [#672], [#674] - -Props [@alpipego][gh-alpipego], [@costdev][gh-costdev], [@jegrandet][gh-jegrandet], [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera] - -[#674]: https://github.com/WordPress/Requests/pull/674 -[#672]: https://github.com/WordPress/Requests/pull/672 -[#671]: https://github.com/WordPress/Requests/pull/671 -[#670]: https://github.com/WordPress/Requests/pull/670 -[#669]: https://github.com/WordPress/Requests/pull/669 -[#662]: https://github.com/WordPress/Requests/pull/662 -[#661]: https://github.com/WordPress/Requests/pull/661 -[#660]: https://github.com/WordPress/Requests/pull/660 -[#658]: https://github.com/WordPress/Requests/pull/658 -[#655]: https://github.com/WordPress/Requests/pull/655 -[#653]: https://github.com/WordPress/Requests/pull/653 -[#650]: https://github.com/WordPress/Requests/pull/650 -[#649]: https://github.com/WordPress/Requests/pull/649 -[#647]: https://github.com/WordPress/Requests/pull/647 -[#646]: https://github.com/WordPress/Requests/pull/646 -[#635]: https://github.com/WordPress/Requests/issues/635 -[#452]: https://github.com/WordPress/Requests/issues/452 - - -2.0.0 ------ - -### BREAKING CHANGES - -As Requests 2.0.0 is a major release, this version contains breaking changes. There is an [upgrade guide](https://requests.ryanmccue.info/docs/upgrading.html) available to guide you through making the necessary changes in your own code. - -### Overview of changes - -- **New minimum PHP version** - - Support for PHP 5.2 - 5.5 has been dropped. The new minimum supported PHP version is now 5.6. - - Support for HHVM has also been dropped formally now. - - (props [@datagutten][gh-datagutten], [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#378][gh-378], [#470][gh-470], [#509][gh-509]) - -- **New release branch name** - - The stable version of Requests can be found in the `stable` branch (was `master`). - Development of Requests happens in the `develop` branch. - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#463][gh-463], [#490][gh-490]) - -- **All code is now namespaced (PSR-4)** - - The code within the Requests library has all been namespaced and now lives in the `WpOrg\Requests` namespace. - - The namespaced classes can be found in the `src` directory. The old `library` directory and the files within are deprecated. - - For a number of classes, some subtle changes have also been made to their base class name, like renaming the `Hooker` interface to `HookManager`. - - A full backward-compatibility layer is available and using the non-namespaced class names will still work during the 2.x and 3.x release cycles, though a deprecation notice will be thrown the first time a class using one of the old PSR-0 based class names is requested. - For the lifetime of Requests 2.x, the deprecation notices can be disabled by defining a global `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and -setting the value of this constant to `true`. - - A complete "translation table" between the Requests 1.x and 2.x class names is available in the [upgrade guide](https://requests.ryanmccue.info/docs/upgrading.html). - - Users of the Requests native custom autoloader will need to adjust their code to initialize the autoloader: - ```php - // OLD: Using the custom autoloader in Requests 1.x. - require_once 'path/to/Requests/library/Requests.php'; - Requests::register_autoloader(); - - // NEW: Using the custom autoloader in Requests 2.x. - require_once 'path/to/Requests/src/Autoload.php'; - WpOrg\Requests\Autoload::register(); - ``` - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#503][gh-503], [#519][gh-519], [#586][gh-586], [#587][gh-587], [#594][gh-594]) - -- **A large number of classes have been marked as `final`** - - Marking a class as `final` prohibits extending it. - - These changes were made after researching which classes were being extended in userland code and due diligence has been applied before making these changes. If this change is causing a problem we didn't anticipate, please [open an issue to report it](https://github.com/WordPress/Requests/issues/new/choose). - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#514][gh-514], [#534][gh-534]) - -- **Input validation** - - All typical entry point methods in Requests will now, directly or indirectly, validate the received input parameters for being of the correct type. - When an incorrect parameter type is received, a catchable `WpOrg\Requests\Exception\InvalidArgument` exception will be thrown. - - The input validation has been set up to be reasonably liberal, so if Requests was being used as per the documentation, this change should not affect you. - If you still find the input validation to be too strict and you have a good use-case of why it should be loosened for a particular entry point, please [open an issue to discuss this](https://github.com/WordPress/Requests/issues/new/choose). - - The code within Requests itself has also received various improvements to be more type safe. - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#499][gh-499], [#542][gh-542], [#547][gh-547], [#558][gh-558], [#572][gh-572], [#573][gh-573], [#574][gh-574], [#591][gh-591], [#592][gh-592], [#593][gh-593], [#601][gh-601], [#602][gh-602], [#603][gh-603], [#604][gh-604], [#605][gh-605], [#609][gh-609], [#610][gh-610], [#611][gh-611], [#613][gh-613], [#614][gh-614], [#615][gh-615], [#620][gh-620], [#621][gh-621], [#629][gh-629]) - -- **Update bundled certificates** - - The bundled certificates were updated with the latest version available (published 2021-10-26). - - Previously the bundled certificates in Requests would include a small subset of expired certificates for legacy reasons. - This is no longer the case as of Requests 2.0.0. - - > :warning: **Note**: the included certificates bundle is only intended as a fallback. - > - > This fallback should only be used for servers that are not properly configured for SSL verification. A continuously managed server should provide a more up-to-date certificate authority list than a software library which only gets updates once in a while. - > - > Setting the `$options['verify']` key to `true` when initiating a request enables certificate verification using the certificate authority list provided by the server environment, which is recommended. - - The [documentation regarding Secure Requests with SSL](https://requests.ryanmccue.info/docs/usage-advanced.html#secure-requests-with-ssl) has also been updated to reflect this and it is recommended to have a read through. - - The included certificates _file_ has now also been moved to a dedicated `/certificates` directory off the project root. - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [@wojsmol][gh-wojsmol], [@ZsgsDesign][gh-ZsgsDesign], [#535][gh-535], [#571][gh-571], [#577][gh-577], [#622][gh-622], [#632][gh-632]) - -- **New functionality** - - The following new functionality has been added: - - A `public static` `WpOrg\Requests\Requests::has_capabilities($capabilities = array())` method is now available to check whether there is a transport available which supports the requested capabilities. - - A `public` `WpOrg\Requests\Response::decode_body($associative = true, $depth = 512, $options = 0)` method is now available to handle JSON-decoding a response body. - The method parameters correspond to the parameters of the PHP native [`json_decode()`](https://php.net/json-decode) function. - The method will throw an `WpOrg\Requests\Exception` when the response body is not valid JSON. - - A `WpOrg\Requests\Capability` interface. This interface provides constants for the known capabilities. Transports can be tested whether or not they support these capabilities. - Currently, the only capability supported is `Capability::SSL`. - - A `WpOrg\Requests\Port` class. This class encapsulates typical port numbers as constants and offers a `static` `Port::get($type)` method to retrieve a port number based on a request type. - Using this class when referring to port numbers is recommended. - - An `WpOrg\Requests\Exceptions\InvalidArgument` class. This class is intended for internal use only. - - An `WpOrg\Requests\Utility\InputValidator` class with helper methods for input validation. This class is intended for internal use only. - - (props [@ccrims0n][gh-ccrims0n], [@dd32][gh-dd32], [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#167][gh-167], [#214][gh-214], [#250][gh-250], [#251][gh-251], [#492][gh-492], [#499][gh-499], [#538][gh-538], [#542][gh-542], [#547][gh-547], [#559][gh-559]) - -- **Changed functionality** - - - The `WpOrg\Requests\Requests::decompress()` method has been fixed to recognize more compression levels and handle these correctly. - - The method signature of the `WpOrg\Requests\Transport::test()` interface method has been adjusted to enforce support for an optional `$capabilities` parameter. - The Request native `WpOrg\Requests\Transport\Curl::test()` and `WpOrg\Requests\Transport\Fsockopen::test()` methods both already supported this parameter. - - The `WpOrg\Requests\Transport\Curl::request()` and the `WpOrg\Requests\Transport\Fsockopen::request()` methods will now throw an `WpOrg\Requests\Exception` when the `$options['filename']` contains an invalid path. - - The `WpOrg\Requests\Transport\Curl::request()` method will no longer set the `CURLOPT_REFERER` option. - - The default value of the `$key` parameter in the `WpOrg\Requests\Cookie\Jar::normalize_cookie()` method has been changed from `null` to an empty string. - - (props [@datagutten][gh-datagutten], [@dustinrue][gh-dustinrue], [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [@soulseekah][gh-soulseekah], [@twdnhfr][gh-twdnhfr], [#301][gh-301], [#309][gh-309], [#379][gh-379], [#444][gh-444], [#492][gh-492], [#610][gh-610]) - -- **Removed functionality** - - The following methods, which were deprecated during the 1.x cycle, have now been removed: - - `Requests::flattern()`, use `WpOrg\Requests\Requests::flatten()` instead. - - `Requests_Cookie::formatForHeader()`, use `WpOrg\Requests\Cookie::format_for_header()` instead. - - `Requests_Cookie::formatForSetCookie()`, use `WpOrg\Requests\Cookie::format_for_set_cookie()` instead. - - `Requests_Cookie::parseFromHeaders()`, use `WpOrg\Requests\Cookie::parse_from_headers()` instead. - - `Requests_Cookie_Jar::normalizeCookie()`, use `WpOrg\Requests\Cookie\Jar::normalize_cookie()` instead - - A duplicate method has been removed: - - `Requests::match_domain()`, use `WpOrg\Requests\Ssl::match_domain()` instead. - - A redundant method has been removed: - - `Hooks::__construct()`. - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#510][gh-510], [#525][gh-525], [#617][gh-617]) - -- **Compatibility with PHP 8.0 named parameters** - - All parameter names have been reviewed to prevent issues for users using PHP 8.0 named parameters and where relevant, a number of parameter names have been changed. - - After this release, a parameter name rename will be treated as a breaking change (reserved for major releases) and will be marked as such in the changelog. - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#533][gh-533], [#560][gh-560], [#561][gh-561], [#599][gh-599], [#612][gh-612]) - -- **PHP 8.1 compatibility** - - All known PHP 8.1 compatibility issues have been fixed and tests are now running (and passing) against PHP 8.1. - - In case you still run into a PHP 8.1 deprecation notice or other PHP 8.1 related issue, please [open an issue to report it](https://github.com/WordPress/Requests/issues/new/choose). - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#498][gh-498], [#499][gh-499], [#500][gh-500], [#501][gh-501], [#505][gh-505], [#634][gh-634]) - -- **Updated documentation** - - The [documentation website](https://requests.ryanmccue.info/) has been updated to reflect all the changes in Requests 2.0.0. - - The [API documentation for Requests 2.x](https://requests.ryanmccue.info/api-2.x/) is now generated using [phpDocumentor](https://www.phpdoc.org/) :heart: and available on the website. - For the time being, the [Requests 1.x API documentation](https://requests.ryanmccue.info/api/) will still be available on the website as well. - - (props [@costdev][gh-costdev], [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [@szepeviktor][gh-szepeviktor], [#476][gh-476], [#480][gh-480], [#489][gh-489], [#495][gh-495], [#526][gh-526], [#528][gh-528], [#532][gh-532], [#543][gh-543], [#562][gh-562], [#578][gh-578], [#590][gh-590], [#606][gh-606], [#607][gh-607], [#608][gh-608], [#618][gh-618], [#622][gh-622], [#625][gh-625], [#626][gh-626], [#630][gh-630], [#642][gh-642]) - -- **General housekeeping** - - - In a number of places, code modernizations, possible now the minimum PHP version has gone up to PHP 5.6, have been applied. - ([#504][gh-504], [#506][gh-506], [#512][gh-512], [#539][gh-539], [#541][gh-541], [#599][gh-599], [#623][gh-623]) - - - Lots of improvements were made to render the tests more reliable and increase the coverage. - ([#446][gh-446], [#459][gh-459], [#472][gh-472], [#503][gh-503], [#508][gh-508], [#511][gh-511], [#520][gh-520], [#521][gh-521], [#548][gh-548], [#549][gh-549], [#550][gh-550], [#551][gh-551], [#552][gh-552], [#553][gh-553], [#554][gh-554], [#555][gh-555], [#556][gh-556], [#557][gh-557], [#558][gh-558], [#566][gh-566], [#581][gh-581], [#591][gh-591], [#595][gh-595], [#640][gh-640]) - - - The move for all CI to GitHub Actions has been finalized. Travis is dead, long live Travis and thanks for all the fish. - ([#447][gh-447], [#575][gh-575], [#579][gh-579]) - - - A GitHub Actions workflow has been put in place to allow for automatically updating the website on releases. - This should allow for more rapid releases from now on. - ([#466][gh-466], [#544][gh-544], [#545][gh-545], [#563][gh-563], [#569][gh-569], [#583][gh-583], [#626][gh-626]) - - - Development-only dependencies have been updated. - ([#516][gh-516], [#517][gh-517]) - - - Various other general housekeeping and improvements for contributors. - ([#488][gh-488], [#491][gh-491], [#523][gh-523], [#513][gh-513], [#515][gh-515], [#522][gh-522], [#524][gh-524], [#531][gh-531], [#535][gh-535], [#536][gh-536], [#537][gh-537], [#540][gh-540], [#588][gh-588], [#616][gh-616]) - - (props [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera]) - -[gh-642]: https://github.com/WordPress/Requests/pull/642 -[gh-640]: https://github.com/WordPress/Requests/pull/640 -[gh-634]: https://github.com/WordPress/Requests/pull/634 -[gh-632]: https://github.com/WordPress/Requests/pull/632 -[gh-630]: https://github.com/WordPress/Requests/pull/630 -[gh-629]: https://github.com/WordPress/Requests/pull/629 -[gh-626]: https://github.com/WordPress/Requests/pull/626 -[gh-625]: https://github.com/WordPress/Requests/pull/625 -[gh-623]: https://github.com/WordPress/Requests/pull/623 -[gh-622]: https://github.com/WordPress/Requests/pull/622 -[gh-621]: https://github.com/WordPress/Requests/pull/621 -[gh-620]: https://github.com/WordPress/Requests/pull/620 -[gh-618]: https://github.com/WordPress/Requests/pull/618 -[gh-617]: https://github.com/WordPress/Requests/pull/617 -[gh-616]: https://github.com/WordPress/Requests/pull/616 -[gh-615]: https://github.com/WordPress/Requests/pull/615 -[gh-614]: https://github.com/WordPress/Requests/pull/614 -[gh-613]: https://github.com/WordPress/Requests/pull/613 -[gh-612]: https://github.com/WordPress/Requests/pull/612 -[gh-611]: https://github.com/WordPress/Requests/pull/611 -[gh-610]: https://github.com/WordPress/Requests/pull/610 -[gh-609]: https://github.com/WordPress/Requests/pull/609 -[gh-608]: https://github.com/WordPress/Requests/pull/608 -[gh-607]: https://github.com/WordPress/Requests/pull/607 -[gh-606]: https://github.com/WordPress/Requests/pull/606 -[gh-605]: https://github.com/WordPress/Requests/pull/605 -[gh-604]: https://github.com/WordPress/Requests/pull/604 -[gh-603]: https://github.com/WordPress/Requests/pull/603 -[gh-602]: https://github.com/WordPress/Requests/pull/602 -[gh-601]: https://github.com/WordPress/Requests/pull/601 -[gh-599]: https://github.com/WordPress/Requests/pull/599 -[gh-595]: https://github.com/WordPress/Requests/pull/595 -[gh-594]: https://github.com/WordPress/Requests/pull/594 -[gh-593]: https://github.com/WordPress/Requests/issues/593 -[gh-592]: https://github.com/WordPress/Requests/pull/592 -[gh-591]: https://github.com/WordPress/Requests/pull/591 -[gh-590]: https://github.com/WordPress/Requests/issues/590 -[gh-588]: https://github.com/WordPress/Requests/pull/588 -[gh-587]: https://github.com/WordPress/Requests/pull/587 -[gh-586]: https://github.com/WordPress/Requests/pull/586 -[gh-583]: https://github.com/WordPress/Requests/pull/583 -[gh-581]: https://github.com/WordPress/Requests/pull/581 -[gh-579]: https://github.com/WordPress/Requests/pull/579 -[gh-578]: https://github.com/WordPress/Requests/pull/578 -[gh-577]: https://github.com/WordPress/Requests/pull/577 -[gh-575]: https://github.com/WordPress/Requests/pull/575 -[gh-574]: https://github.com/WordPress/Requests/pull/574 -[gh-573]: https://github.com/WordPress/Requests/pull/573 -[gh-572]: https://github.com/WordPress/Requests/pull/572 -[gh-571]: https://github.com/WordPress/Requests/pull/571 -[gh-569]: https://github.com/WordPress/Requests/pull/569 -[gh-566]: https://github.com/WordPress/Requests/pull/566 -[gh-563]: https://github.com/WordPress/Requests/pull/563 -[gh-562]: https://github.com/WordPress/Requests/pull/562 -[gh-561]: https://github.com/WordPress/Requests/pull/561 -[gh-560]: https://github.com/WordPress/Requests/pull/560 -[gh-559]: https://github.com/WordPress/Requests/pull/559 -[gh-558]: https://github.com/WordPress/Requests/pull/558 -[gh-557]: https://github.com/WordPress/Requests/pull/557 -[gh-556]: https://github.com/WordPress/Requests/pull/556 -[gh-555]: https://github.com/WordPress/Requests/pull/555 -[gh-554]: https://github.com/WordPress/Requests/pull/554 -[gh-553]: https://github.com/WordPress/Requests/pull/553 -[gh-552]: https://github.com/WordPress/Requests/pull/552 -[gh-551]: https://github.com/WordPress/Requests/pull/551 -[gh-550]: https://github.com/WordPress/Requests/pull/550 -[gh-549]: https://github.com/WordPress/Requests/pull/549 -[gh-548]: https://github.com/WordPress/Requests/pull/548 -[gh-547]: https://github.com/WordPress/Requests/pull/547 -[gh-545]: https://github.com/WordPress/Requests/pull/545 -[gh-544]: https://github.com/WordPress/Requests/pull/544 -[gh-543]: https://github.com/WordPress/Requests/pull/543 -[gh-542]: https://github.com/WordPress/Requests/pull/542 -[gh-541]: https://github.com/WordPress/Requests/pull/541 -[gh-540]: https://github.com/WordPress/Requests/pull/540 -[gh-539]: https://github.com/WordPress/Requests/pull/539 -[gh-538]: https://github.com/WordPress/Requests/pull/538 -[gh-537]: https://github.com/WordPress/Requests/pull/537 -[gh-536]: https://github.com/WordPress/Requests/pull/536 -[gh-535]: https://github.com/WordPress/Requests/pull/535 -[gh-534]: https://github.com/WordPress/Requests/pull/534 -[gh-533]: https://github.com/WordPress/Requests/issues/533 -[gh-532]: https://github.com/WordPress/Requests/pull/532 -[gh-531]: https://github.com/WordPress/Requests/pull/531 -[gh-528]: https://github.com/WordPress/Requests/pull/528 -[gh-526]: https://github.com/WordPress/Requests/pull/526 -[gh-525]: https://github.com/WordPress/Requests/pull/525 -[gh-524]: https://github.com/WordPress/Requests/pull/524 -[gh-523]: https://github.com/WordPress/Requests/pull/523 -[gh-522]: https://github.com/WordPress/Requests/pull/522 -[gh-521]: https://github.com/WordPress/Requests/pull/521 -[gh-520]: https://github.com/WordPress/Requests/pull/520 -[gh-519]: https://github.com/WordPress/Requests/pull/519 -[gh-517]: https://github.com/WordPress/Requests/pull/517 -[gh-516]: https://github.com/WordPress/Requests/pull/516 -[gh-515]: https://github.com/WordPress/Requests/issues/515 -[gh-514]: https://github.com/WordPress/Requests/issues/514 -[gh-513]: https://github.com/WordPress/Requests/issues/513 -[gh-512]: https://github.com/WordPress/Requests/issues/512 -[gh-511]: https://github.com/WordPress/Requests/pull/511 -[gh-510]: https://github.com/WordPress/Requests/pull/510 -[gh-509]: https://github.com/WordPress/Requests/pull/509 -[gh-508]: https://github.com/WordPress/Requests/pull/508 -[gh-506]: https://github.com/WordPress/Requests/pull/506 -[gh-505]: https://github.com/WordPress/Requests/pull/505 -[gh-504]: https://github.com/WordPress/Requests/pull/504 -[gh-503]: https://github.com/WordPress/Requests/pull/503 -[gh-501]: https://github.com/WordPress/Requests/pull/501 -[gh-500]: https://github.com/WordPress/Requests/pull/500 -[gh-499]: https://github.com/WordPress/Requests/pull/499 -[gh-498]: https://github.com/WordPress/Requests/issues/498 -[gh-498]: https://github.com/WordPress/Requests/issues/495 -[gh-492]: https://github.com/WordPress/Requests/pull/492 -[gh-491]: https://github.com/WordPress/Requests/pull/491 -[gh-490]: https://github.com/WordPress/Requests/pull/490 -[gh-489]: https://github.com/WordPress/Requests/pull/489 -[gh-488]: https://github.com/WordPress/Requests/pull/488 -[gh-480]: https://github.com/WordPress/Requests/issues/480 -[gh-476]: https://github.com/WordPress/Requests/issues/476 -[gh-472]: https://github.com/WordPress/Requests/issues/472 -[gh-470]: https://github.com/WordPress/Requests/pull/470 -[gh-466]: https://github.com/WordPress/Requests/issues/466 -[gh-463]: https://github.com/WordPress/Requests/issues/463 -[gh-460]: https://github.com/WordPress/Requests/issues/460 -[gh-459]: https://github.com/WordPress/Requests/issues/459 -[gh-447]: https://github.com/WordPress/Requests/pull/447 -[gh-446]: https://github.com/WordPress/Requests/pull/446 -[gh-444]: https://github.com/WordPress/Requests/pull/444 -[gh-379]: https://github.com/WordPress/Requests/pull/379 -[gh-378]: https://github.com/WordPress/Requests/issues/378 -[gh-309]: https://github.com/WordPress/Requests/pull/309 -[gh-301]: https://github.com/WordPress/Requests/issues/301 -[gh-251]: https://github.com/WordPress/Requests/pull/251 -[gh-250]: https://github.com/WordPress/Requests/issues/250 -[gh-214]: https://github.com/WordPress/Requests/pull/214 -[gh-167]: https://github.com/WordPress/Requests/issues/167 - -1.8.1 ------ - -### Overview of changes -- The `Requests::VERSION` constant has been updated to reflect the actual version for the release. [@jrfnl][gh-jrfnl], [#485][gh-485] -- Update the `.gitattributes` file to include fewer files in the distribution. [@mbabker][gh-mbabker], [#484][gh-484] -- Added a release checklist. [@jrfnl][gh-jrfnl], [#483][gh-483] -- Various minor updates to the documentation and the website. [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#477][gh-477], [#478][gh-478], [#479][gh-479], [#481][gh-481], [#482][gh-482] - -[gh-477]: https://github.com/WordPress/Requests/issues/477 -[gh-478]: https://github.com/WordPress/Requests/issues/478 -[gh-479]: https://github.com/WordPress/Requests/issues/479 -[gh-481]: https://github.com/WordPress/Requests/issues/481 -[gh-482]: https://github.com/WordPress/Requests/issues/482 -[gh-483]: https://github.com/WordPress/Requests/issues/483 -[gh-484]: https://github.com/WordPress/Requests/issues/484 -[gh-485]: https://github.com/WordPress/Requests/issues/485 - - -1.8.0 ------ - -### IMPORTANT NOTES - -#### Last release supporting PHP 5.2 - 5.5 - - Release 1.8.0 will be the last release with compatibility for PHP 5.2 - 5.5. With the next release (v2.0.0), the minimum PHP version will be bumped to 5.6. - -#### Last release supporting PEAR distribution - - Release 1.8.0 will be the last release to be distributed via PEAR. From release 2.0.0 onwards, consumers of this library will have to switch to Composer to receive updates. - -### Overview of changes - -- **[SECURITY FIX] Disable deserialization in `FilteredIterator`** - - A `Deserialization of Untrusted Data` weakness was found in the `FilteredIterator` class. - - This security vulnerability was first reported to the WordPress project. The security fix applied to WordPress has been ported back into the library. - - GitHub security advisory: [Insecure Deserialization of untrusted data](https://github.com/WordPress/Requests/security/advisories/GHSA-52qp-jpq7-6c54) - - CVE: [CVE-2021-29476 - Deserialization of Untrusted Data](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-29476) - - Related WordPress CVE: [https://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-28032](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2020-28032) - - (props [@dd32][gh-dd32], [@desrosj][gh-desrosj], [@jrfnl][gh-jrfnl], [@peterwilsoncc][gh-peterwilsoncc], [@SergeyBiryukov][gh-SergeyBiryukov], [@whyisjake][gh-whyisjake], [@xknown][gh-xknown], [#421][gh-421], [#422][gh-422]) - - -- **Repository moved to `WordPress\Requests`** - - The `Requests` library has been moved to the WordPress GitHub organization and can now be found under `https://github.com/WordPress/Requests`. - - All links in code and documentation were updated accordingly. - - Note: the Composer package name remains unchanged ([`rmccue/requests`](https://packagist.org/packages/rmccue/requests)), as well as the documentation site ([requests.ryanmccue.info](https://requests.ryanmccue.info/)). - - (props [@dd32][gh-dd32], [@JustinyAhin][gh-JustinyAhin], [@jrfnl][gh-jrfnl], [@rmccue][gh-rmccue], [#440][gh-440], [#441][gh-441], [#448][gh-448]) - - -- **Manage `"Expect"` header with `cURL` transport** - - By default, `cURL` adds a `Expect: 100-Continue` header to certain requests. This can add as much as a second delay to requests done using `cURL`. This is [discussed on the cURL mailing list](https://curl.se/mail/lib-2017-07/0013.html). - - To prevent this, `Requests` now adds an empty `"Expect"` header to requests that are smaller than 1 MB and use HTTP/1.1. - - (props [@carlalexander][gh-carlalexander], [@schlessera][gh-schlessera], [@TimothyBJacobs][gh-TimothyBJacobs], [#453][gh-453], [#454][gh-454], [#469][gh-469]) - - -- **Update bundled certificates as of 2021-02-12** - - The bundled certificates were updated. A small subset of expired certificates are still included for legacy reasons (and support). - - (props [@ozh][gh-ozh], [@patmead][gh-patmead], [@schlessera][gh-schlessera], [@todeveni][gh-todeveni], [#385][gh-385], [#398][gh-398], [#451][gh-451]) - - -- **Add required `Content-*` headers for empty `POST` requests** - - Sends the `Content-Length` and `Content-Type` headers even for empty `POST` requests, as the length is expected as per [RFC2616 Section 14.13](https://tools.ietf.org/html/rfc2616#section-14.13): - ``` - Content-Length header "SHOULD" be included. In practice, it is not - used for GET nor HEAD requests, but is expected for POST requests. - ``` - - (props [@dd32][gh-dd32], [@gstrauss][gh-gstrauss], [@jrfnl][gh-jrfnl], [@soulseekah][gh-soulseekah], [#248][gh-248], [#249][gh-249], [#318][gh-318], [#368][gh-368]) - - -- **Ignore locale when creating the HTTP version string from a float** - - The previous behavior allowed for the locale to mess up the float to string conversion resulting in a `GET / HTTP/1,1` instead of `GET / HTTP/1.1` request. - - (props [@tonebender][gh-tonebender], [@Zegnat][gh-Zegnat], [#335][gh-335], [#339][gh-339]) - - -- **Make `verify => false` work with `fsockopen`** - - This allows the `fsockopen` transport now to ignore SSL failures when requested. - - (props [@soulseekah][gh-soulseekah], [#310][gh-310], [#311][gh-311]) - - -- **Only include port number in the `Host` header if it differs from the default** - - The code was not violating the RFC per se, but also not following standard practice of leaving the port off when it is the default port for the scheme, which could lead to connectivity issues. - - (props [@amandato][gh-amandato], [@dd32][gh-dd32], [#238][gh-238]) - - -- **Fix PHP cross-version compatibility** - - Important fixes have been made to improve cross-version compatibility of the code across all supported PHP versions. - - - Use documented order for `implode()` arguments. - - Harden type handling when no domain was passed. - - Explicitly cast `$url` property to `string` in `Requests::parse_response()`. - - Initialize `$body` property to an empty string in `Requests::parse_response()`. - - Ensure the stream handle is valid before trying to close it. - - Ensure the `$callback` in the `FilteredIterator` is callable before calling it. - - (props [@aaronjorbin][gh-aaronjorbin], [@jrfnl][gh-jrfnl], [#346][gh-346], [#370][gh-370], [#425][gh-425], [#426][gh-426], [#456][gh-456], [#457][gh-457]) - - -- **Improve testing** - - Lots of improvements were made to render the tests more reliable and increase the coverage. - - And to top it all off, all tests are now run against all supported PHP versions, including PHP 8.0. - - (props [@datagutten][gh-datagutten], [@jrfnl][gh-jrfnl], [@schlessera][gh-schlessera], [#345][gh-345], [#351][gh-351], [#355][gh-355], [#366][gh-366], [#412][gh-412], [#414][gh-414], [#445][gh-445], [#458][gh-458], [#464][gh-464]) - - -- **Improve code quality and style** - - A whole swoop of changes has been made to harden the code and make it more consistent. - - The code style has been made consistent across both code and tests and is now enforced via a custom PHPCS rule set. - - The WordPress Coding Standards were chosen as the basis for the code style checks as most contributors to this library originate from the WordPress community and will be familiar with this code style. - - Main differences from the WordPress Coding Standards based on discussions and an analysis of the code styles already in use: - - - No whitespace on the inside of parentheses. - - No Yoda conditions. - - A more detailed overview of the decisions that went into the final code style rules can be found at [#434][gh-434]. - - (props [@jrfnl][gh-jrfnl], [@KasperFranz][gh-KasperFranz], [@ozh][gh-ozh], [@schlessera][gh-schlessera], [@TysonAndre][gh-TysonAndre], [#263][gh-263], [#296][gh-296], [#328][gh-328], [#358][gh-358], [#359][gh-359], [#360][gh-360], [#361][gh-361], [#362][gh-362], [#363][gh-363], [#364][gh-364], [#386][gh-386], [#396][gh-396], [#399][gh-399], [#400][gh-400], [#401][gh-401], [#402][gh-402], [#403][gh-403], [#404][gh-404], [#405][gh-405], [#406][gh-406], [#408][gh-408], [#409][gh-409], [#410][gh-410], [#411][gh-411], [#413][gh-413], [#415][gh-415], [#416][gh-416], [#417][gh-417], [#423][gh-423], [#424][gh-424], [#434][gh-434]) - - -- **Replace Travis CI with GitHub Actions (partial)** - - The entire CI setup is gradually being moved from Travis CI to GitHub Actions. - - At this point, GitHub Actions takes over the CI from PHP 5.5 onwards, leaving Travis CI as a fallback for lower PHP versions. - - This move will be completed after the planned minimum version bump to PHP 5.6+ with the next release, at which point we will get rid of all the remaining Travis CI integrations. - - (props [@dd32][gh-dd32], [@desrosj][gh-desrosj], [@jrfnl][gh-jrfnl], [@ntwb][gh-ntwb], [@ozh][gh-ozh], [@schlessera][gh-schlessera], [@TimothyBJacobs][gh-TimothyBJacobs], [@TysonAndre][gh-TysonAndre], [#280][gh-280], [#298][gh-298], [#302][gh-302], [#303][gh-303], [#352][gh-352], [#353][gh-353], [#354][gh-354], [#356][gh-356], [#388][gh-388], [#397][gh-397], [#428][gh-428], [#436][gh-436], [#439][gh-439], [#461][gh-461], [#467][gh-467]) - - -- **Update and improve documentation** - - Use clearer and more inclusive language. - - Update the GitHub Pages site. - - Update content and various tweaks to the markdown. - - Fix code blocks in `README.md` file. - - Add pagination to documentation pages. - - (props [@desrosj][gh-desrosj], [@jrfnl][gh-jrfnl], [@JustinyAhin][gh-JustinyAhin], [@tnorthcutt][gh-tnorthcutt], [#334][gh-334], [#367][gh-367], [#387][gh-387], [#443][gh-443], [#462][gh-462], [#465][gh-465], [#468][gh-468], [#471][gh-471] ) - -[gh-194]: https://github.com/WordPress/Requests/issues/194 -[gh-238]: https://github.com/WordPress/Requests/issues/238 -[gh-248]: https://github.com/WordPress/Requests/issues/248 -[gh-249]: https://github.com/WordPress/Requests/issues/249 -[gh-263]: https://github.com/WordPress/Requests/issues/263 -[gh-280]: https://github.com/WordPress/Requests/issues/280 -[gh-296]: https://github.com/WordPress/Requests/issues/296 -[gh-298]: https://github.com/WordPress/Requests/issues/298 -[gh-302]: https://github.com/WordPress/Requests/issues/302 -[gh-303]: https://github.com/WordPress/Requests/issues/303 -[gh-310]: https://github.com/WordPress/Requests/issues/310 -[gh-311]: https://github.com/WordPress/Requests/issues/311 -[gh-318]: https://github.com/WordPress/Requests/issues/318 -[gh-328]: https://github.com/WordPress/Requests/issues/328 -[gh-334]: https://github.com/WordPress/Requests/issues/334 -[gh-335]: https://github.com/WordPress/Requests/issues/335 -[gh-339]: https://github.com/WordPress/Requests/issues/339 -[gh-345]: https://github.com/WordPress/Requests/issues/345 -[gh-346]: https://github.com/WordPress/Requests/issues/346 -[gh-351]: https://github.com/WordPress/Requests/issues/351 -[gh-352]: https://github.com/WordPress/Requests/issues/352 -[gh-353]: https://github.com/WordPress/Requests/issues/353 -[gh-354]: https://github.com/WordPress/Requests/issues/354 -[gh-355]: https://github.com/WordPress/Requests/issues/355 -[gh-356]: https://github.com/WordPress/Requests/issues/356 -[gh-358]: https://github.com/WordPress/Requests/issues/358 -[gh-359]: https://github.com/WordPress/Requests/issues/359 -[gh-360]: https://github.com/WordPress/Requests/issues/360 -[gh-361]: https://github.com/WordPress/Requests/issues/361 -[gh-362]: https://github.com/WordPress/Requests/issues/362 -[gh-363]: https://github.com/WordPress/Requests/issues/363 -[gh-364]: https://github.com/WordPress/Requests/issues/364 -[gh-366]: https://github.com/WordPress/Requests/issues/366 -[gh-367]: https://github.com/WordPress/Requests/issues/367 -[gh-367]: https://github.com/WordPress/Requests/issues/367 -[gh-368]: https://github.com/WordPress/Requests/issues/368 -[gh-370]: https://github.com/WordPress/Requests/issues/370 -[gh-385]: https://github.com/WordPress/Requests/issues/385 -[gh-386]: https://github.com/WordPress/Requests/issues/386 -[gh-387]: https://github.com/WordPress/Requests/issues/387 -[gh-388]: https://github.com/WordPress/Requests/issues/388 -[gh-396]: https://github.com/WordPress/Requests/issues/396 -[gh-397]: https://github.com/WordPress/Requests/issues/397 -[gh-398]: https://github.com/WordPress/Requests/issues/398 -[gh-399]: https://github.com/WordPress/Requests/issues/399 -[gh-400]: https://github.com/WordPress/Requests/issues/400 -[gh-401]: https://github.com/WordPress/Requests/issues/401 -[gh-402]: https://github.com/WordPress/Requests/issues/402 -[gh-403]: https://github.com/WordPress/Requests/issues/403 -[gh-404]: https://github.com/WordPress/Requests/issues/404 -[gh-405]: https://github.com/WordPress/Requests/issues/405 -[gh-406]: https://github.com/WordPress/Requests/issues/406 -[gh-408]: https://github.com/WordPress/Requests/issues/408 -[gh-409]: https://github.com/WordPress/Requests/issues/409 -[gh-410]: https://github.com/WordPress/Requests/issues/410 -[gh-411]: https://github.com/WordPress/Requests/issues/411 -[gh-412]: https://github.com/WordPress/Requests/issues/412 -[gh-413]: https://github.com/WordPress/Requests/issues/413 -[gh-414]: https://github.com/WordPress/Requests/issues/414 -[gh-415]: https://github.com/WordPress/Requests/issues/415 -[gh-416]: https://github.com/WordPress/Requests/issues/416 -[gh-417]: https://github.com/WordPress/Requests/issues/417 -[gh-421]: https://github.com/WordPress/Requests/issues/421 -[gh-422]: https://github.com/WordPress/Requests/issues/422 -[gh-423]: https://github.com/WordPress/Requests/issues/423 -[gh-424]: https://github.com/WordPress/Requests/issues/424 -[gh-425]: https://github.com/WordPress/Requests/issues/425 -[gh-426]: https://github.com/WordPress/Requests/issues/426 -[gh-428]: https://github.com/WordPress/Requests/issues/428 -[gh-434]: https://github.com/WordPress/Requests/issues/434 -[gh-436]: https://github.com/WordPress/Requests/issues/436 -[gh-439]: https://github.com/WordPress/Requests/issues/439 -[gh-440]: https://github.com/WordPress/Requests/issues/440 -[gh-441]: https://github.com/WordPress/Requests/issues/441 -[gh-443]: https://github.com/WordPress/Requests/issues/443 -[gh-445]: https://github.com/WordPress/Requests/issues/445 -[gh-448]: https://github.com/WordPress/Requests/issues/448 -[gh-451]: https://github.com/WordPress/Requests/issues/451 -[gh-453]: https://github.com/WordPress/Requests/issues/453 -[gh-454]: https://github.com/WordPress/Requests/issues/454 -[gh-456]: https://github.com/WordPress/Requests/issues/456 -[gh-457]: https://github.com/WordPress/Requests/issues/457 -[gh-458]: https://github.com/WordPress/Requests/issues/458 -[gh-461]: https://github.com/WordPress/Requests/issues/461 -[gh-462]: https://github.com/WordPress/Requests/issues/462 -[gh-464]: https://github.com/WordPress/Requests/issues/464 -[gh-465]: https://github.com/WordPress/Requests/issues/465 -[gh-467]: https://github.com/WordPress/Requests/issues/467 -[gh-468]: https://github.com/WordPress/Requests/issues/468 -[gh-469]: https://github.com/WordPress/Requests/issues/469 -[gh-471]: https://github.com/WordPress/Requests/issues/471 - -1.7.0 ------ - -- Add support for HHVM and PHP 7 - - Requests is now tested against both HHVM and PHP 7, and they are supported as - first-party platforms. - - (props [@rmccue][gh-rmccue], [#106][gh-106], [#176][gh-176]) - -- Transfer & connect timeouts, in seconds & milliseconds - - cURL is unable to handle timeouts under a second in DNS lookups, so we round - those up to ensure 1-999ms isn't counted as an instant failure. - - (props [@ozh][gh-ozh], [@rmccue][gh-rmccue], [#97][gh-97], [#216][gh-216]) - -- Rework cookie handling to be more thorough. - - Cookies are now restricted to the same-origin by default, expiration is checked. - - (props [@catharsisjelly][gh-catharsisjelly], [@rmccue][gh-rmccue], [#120][gh-120], [#124][gh-124], [#130][gh-130], [#132][gh-132], [#156][gh-156]) - -- Improve testing - - Tests are now run locally to speed them up, as well as further general - improvements to the quality of the testing suite. There are now also - comprehensive proxy tests to ensure coverage there. - - (props [@rmccue][gh-rmccue], [#75][gh-75], [#107][gh-107], [#170][gh-170], [#177][gh-177], [#181][gh-181], [#183][gh-183], [#185][gh-185], [#196][gh-196], [#202][gh-202], [#203][gh-203]) - -- Support custom HTTP methods - - Previously, custom HTTP methods were only supported on sockets; they are now - supported across all transports. - - (props [@ocean90][gh-ocean90], [#227][gh-227]) - -- Add byte limit option - - (props [@rmccue][gh-rmccue], [#172][gh-172]) - -- Support a Requests_Proxy_HTTP() instance for the proxy setting. - - (props [@ocean90][gh-ocean90], [#223][gh-223]) - -- Add progress hook - - (props [@rmccue][gh-rmccue], [#180][gh-180]) - -- Add a before_redirect hook to alter redirects - - (props [@rmccue][gh-rmccue], [#205][gh-205]) - -- Pass cURL info to after_request - - (props [@rmccue][gh-rmccue], [#206][gh-206]) - -- Remove explicit autoload in Composer installation instructions - - (props [@SlikNL][gh-SlikNL], [#86][gh-86]) - -- Restrict CURLOPT_PROTOCOLS on `defined()` instead of `version_compare()` - - (props [@ozh][gh-ozh], [#92][gh-92]) - -- Fix doc - typo in "Authentication" - - (props [@remik][gh-remik], [#99][gh-99]) - -- Contextually check for a valid transport - - (props [@ozh][gh-ozh], [#101][gh-101]) - -- Follow relative redirects correctly - - (props [@ozh][gh-ozh], [#103][gh-103]) - -- Use cURL's version_number - - (props [@mishan][gh-mishan], [#104][gh-104]) - -- Removed duplicated option docs - - (props [@staabm][gh-staabm], [#112][gh-112]) - -- code styling fixed - - (props [@imsaintx][gh-imsaintx], [#113][gh-113]) - -- Fix IRI "normalization" - - (props [@ozh][gh-ozh], [#128][gh-128]) - -- Mention two PHP extension dependencies in the README. - - (props [@orlitzky][gh-orlitzky], [#136][gh-136]) - -- Ignore coverage report files - - (props [@ozh][gh-ozh], [#148][gh-148]) - -- drop obsolete "return" after throw - - (props [@staabm][gh-staabm], [#150][gh-150]) - -- Updated exception message to specify both http + https - - (props [@beutnagel][gh-beutnagel], [#162][gh-162]) - -- Sets `stream_headers` method to public to allow calling it from other -places. - - (props [@adri][gh-adri], [#158][gh-158]) - -- Remove duplicated stream_get_meta_data call - - (props [@rmccue][gh-rmccue], [#179][gh-179]) - -- Transmits $errno from stream_socket_client in exception - - (props [@laurentmartelli][gh-laurentmartelli], [#174][gh-174]) - -- Correct methods to use snake_case - - (props [@rmccue][gh-rmccue], [#184][gh-184]) - -- Improve code quality - - (props [@rmccue][gh-rmccue], [#186][gh-186]) - -- Update Build Status image - - (props [@rmccue][gh-rmccue], [#187][gh-187]) - -- Fix/Rationalize transports (v2) - - (props [@rmccue][gh-rmccue], [#188][gh-188]) - -- Surface cURL errors - - (props [@ifwe][gh-ifwe], [#194][gh-194]) - -- Fix for memleak and curl_close() never being called - - (props [@kwuerl][gh-kwuerl], [#200][gh-200]) - -- addex how to install with composer - - (props [@royopa][gh-royopa], [#164][gh-164]) - -- Uppercase the method to ensure compatibility - - (props [@rmccue][gh-rmccue], [#207][gh-207]) - -- Store default certificate path - - (props [@rmccue][gh-rmccue], [#210][gh-210]) - -- Force closing keep-alive connections on old cURL - - (props [@rmccue][gh-rmccue], [#211][gh-211]) - -- Docs: Updated HTTP links with HTTPS links where applicable - - (props [@ntwb][gh-ntwb], [#215][gh-215]) - -- Remove the executable bit - - (props [@ocean90][gh-ocean90], [#224][gh-224]) - -- Change more links to HTTPS - - (props [@rmccue][gh-rmccue], [#217][gh-217]) - -- Bail from cURL when either `curl_init()` OR `curl_exec()` are unavailable - - (props [@dd32][gh-dd32], [#230][gh-230]) - -- Disable OpenSSL's internal peer_name checking when `verifyname` is disabled. - - (props [@dd32][gh-dd32], [#239][gh-239]) - -- Only include the port number in the `Host` header when it differs from -default - - (props [@dd32][gh-dd32], [#238][gh-238]) - -- Respect port if specified for HTTPS connections - - (props [@dd32][gh-dd32], [#237][gh-237]) - -- Allow paths starting with a double-slash - - (props [@rmccue][gh-rmccue], [#240][gh-240]) - -- Fixes bug in rfc2616 #3.6.1 implementation. - - (props [@stephenharris][gh-stephenharris], [#236][gh-236], [#3][gh-3]) - -- CURLOPT_HTTPHEADER在php7接受空数组导致php-fpm奔溃 - - (props [@qibinghua][gh-qibinghua], [#219][gh-219]) - -[gh-3]: https://github.com/WordPress/Requests/issues/3 -[gh-75]: https://github.com/WordPress/Requests/issues/75 -[gh-86]: https://github.com/WordPress/Requests/issues/86 -[gh-92]: https://github.com/WordPress/Requests/issues/92 -[gh-97]: https://github.com/WordPress/Requests/issues/97 -[gh-99]: https://github.com/WordPress/Requests/issues/99 -[gh-101]: https://github.com/WordPress/Requests/issues/101 -[gh-103]: https://github.com/WordPress/Requests/issues/103 -[gh-104]: https://github.com/WordPress/Requests/issues/104 -[gh-106]: https://github.com/WordPress/Requests/issues/106 -[gh-107]: https://github.com/WordPress/Requests/issues/107 -[gh-112]: https://github.com/WordPress/Requests/issues/112 -[gh-113]: https://github.com/WordPress/Requests/issues/113 -[gh-120]: https://github.com/WordPress/Requests/issues/120 -[gh-124]: https://github.com/WordPress/Requests/issues/124 -[gh-128]: https://github.com/WordPress/Requests/issues/128 -[gh-130]: https://github.com/WordPress/Requests/issues/130 -[gh-132]: https://github.com/WordPress/Requests/issues/132 -[gh-136]: https://github.com/WordPress/Requests/issues/136 -[gh-148]: https://github.com/WordPress/Requests/issues/148 -[gh-150]: https://github.com/WordPress/Requests/issues/150 -[gh-156]: https://github.com/WordPress/Requests/issues/156 -[gh-158]: https://github.com/WordPress/Requests/issues/158 -[gh-162]: https://github.com/WordPress/Requests/issues/162 -[gh-164]: https://github.com/WordPress/Requests/issues/164 -[gh-170]: https://github.com/WordPress/Requests/issues/170 -[gh-172]: https://github.com/WordPress/Requests/issues/172 -[gh-174]: https://github.com/WordPress/Requests/issues/174 -[gh-176]: https://github.com/WordPress/Requests/issues/176 -[gh-177]: https://github.com/WordPress/Requests/issues/177 -[gh-179]: https://github.com/WordPress/Requests/issues/179 -[gh-180]: https://github.com/WordPress/Requests/issues/180 -[gh-181]: https://github.com/WordPress/Requests/issues/181 -[gh-183]: https://github.com/WordPress/Requests/issues/183 -[gh-184]: https://github.com/WordPress/Requests/issues/184 -[gh-185]: https://github.com/WordPress/Requests/issues/185 -[gh-186]: https://github.com/WordPress/Requests/issues/186 -[gh-187]: https://github.com/WordPress/Requests/issues/187 -[gh-188]: https://github.com/WordPress/Requests/issues/188 -[gh-194]: https://github.com/WordPress/Requests/issues/194 -[gh-196]: https://github.com/WordPress/Requests/issues/196 -[gh-200]: https://github.com/WordPress/Requests/issues/200 -[gh-202]: https://github.com/WordPress/Requests/issues/202 -[gh-203]: https://github.com/WordPress/Requests/issues/203 -[gh-205]: https://github.com/WordPress/Requests/issues/205 -[gh-206]: https://github.com/WordPress/Requests/issues/206 -[gh-207]: https://github.com/WordPress/Requests/issues/207 -[gh-210]: https://github.com/WordPress/Requests/issues/210 -[gh-211]: https://github.com/WordPress/Requests/issues/211 -[gh-215]: https://github.com/WordPress/Requests/issues/215 -[gh-216]: https://github.com/WordPress/Requests/issues/216 -[gh-217]: https://github.com/WordPress/Requests/issues/217 -[gh-219]: https://github.com/WordPress/Requests/issues/219 -[gh-223]: https://github.com/WordPress/Requests/issues/223 -[gh-224]: https://github.com/WordPress/Requests/issues/224 -[gh-227]: https://github.com/WordPress/Requests/issues/227 -[gh-230]: https://github.com/WordPress/Requests/issues/230 -[gh-236]: https://github.com/WordPress/Requests/issues/236 -[gh-237]: https://github.com/WordPress/Requests/issues/237 -[gh-238]: https://github.com/WordPress/Requests/issues/238 -[gh-239]: https://github.com/WordPress/Requests/issues/239 -[gh-240]: https://github.com/WordPress/Requests/issues/240 - -1.6.0 ------ -- [Add multiple request support][#23] - Send multiple HTTP requests with both - fsockopen and cURL, transparently falling back to synchronous when - not supported. - -- [Add proxy support][#70] - HTTP proxies are now natively supported via a - [high-level API][docs/proxy]. Major props to Ozh for his fantastic work - on this. - -- [Verify host name for SSL requests][#63] - Requests is now the first and only - standalone HTTP library to fully verify SSL hostnames even with socket - connections. Thanks to Michael Adams, Dion Hulse, Jon Cave, and Pádraic Brady - for reviewing the crucial code behind this. - -- [Add cookie support][#64] - Adds built-in support for cookies (built entirely - as a high-level API) - -- [Add sessions][#62] - To compliment cookies, [sessions][docs/usage-advanced] - can be created with a base URL and default options, plus a shared cookie jar. - -- Add [PUT][#1], [DELETE][#3], and [PATCH][#2] request support - -- [Add Composer support][#6] - You can now install Requests via the - `rmccue/requests` package on Composer - -[docs/proxy]: https://requests.ryanmccue.info/docs/proxy.html -[docs/usage-advanced]: https://requests.ryanmccue.info/docs/usage-advanced.html - -[#1]: https://github.com/WordPress/Requests/issues/1 -[#2]: https://github.com/WordPress/Requests/issues/2 -[#3]: https://github.com/WordPress/Requests/issues/3 -[#6]: https://github.com/WordPress/Requests/issues/6 -[#9]: https://github.com/WordPress/Requests/issues/9 -[#23]: https://github.com/WordPress/Requests/issues/23 -[#62]: https://github.com/WordPress/Requests/issues/62 -[#63]: https://github.com/WordPress/Requests/issues/63 -[#64]: https://github.com/WordPress/Requests/issues/64 -[#70]: https://github.com/WordPress/Requests/issues/70 - -[View all changes][https://github.com/WordPress/Requests/compare/v1.5.0...v1.6.0] - -1.5.0 ------ -Initial release! - -[gh-aaronjorbin]: https://github.com/aaronjorbin -[gh-adri]: https://github.com/adri -[gh-alpipego]: https://github.com/alpipego/ -[gh-amandato]: https://github.com/amandato -[gh-ayesh]: https://github.com/Ayesh -[gh-beutnagel]: https://github.com/beutnagel -[gh-carlalexander]: https://github.com/carlalexander -[gh-catharsisjelly]: https://github.com/catharsisjelly -[gh-ccrims0n]: https://github.com/ccrims0n -[gh-costdev]: https://github.com/costdev -[gh-datagutten]: https://github.com/datagutten -[gh-dustinrue]: https://github.com/dustinrue -[gh-dd32]: https://github.com/dd32 -[gh-desrosj]: https://github.com/desrosj -[gh-gstrauss]: https://github.com/gstrauss -[gh-ifwe]: https://github.com/ifwe -[gh-imsaintx]: https://github.com/imsaintx -[gh-jegrandet]: https://github.com/jegrandet -[gh-JustinyAhin]: https://github.com/JustinyAhin -[gh-jrfnl]: https://github.com/jrfnl -[gh-KasperFranz]: https://github.com/KasperFranz -[gh-kwuerl]: https://github.com/kwuerl -[gh-laszlof]: https://github.com/laszlof -[gh-laurentmartelli]: https://github.com/laurentmartelli -[gh-mbabker]: https://github.com/mbabker -[gh-mircobabini]: https://github.com/mircobabini -[gh-mishan]: https://github.com/mishan -[gh-ntwb]: https://github.com/ntwb -[gh-ocean90]: https://github.com/ocean90 -[gh-orlitzky]: https://github.com/orlitzky -[gh-ozh]: https://github.com/ozh -[gh-patmead]: https://github.com/patmead -[gh-peterwilsoncc]: https://github.com/peterwilsoncc -[gh-pmbaldha]: https://github.com/pmbaldha -[gh-qibinghua]: https://github.com/qibinghua -[gh-remik]: https://github.com/remik -[gh-rmccue]: https://github.com/rmccue -[gh-royopa]: https://github.com/royopa -[gh-schlessera]: https://github.com/schlessera -[gh-SergeyBiryukov]: https://github.com/SergeyBiryukov -[gh-SlikNL]: https://github.com/SlikNL -[gh-soulseekah]: https://github.com/soulseekah -[gh-staabm]: https://github.com/staabm -[gh-stephenharris]: https://github.com/stephenharris -[gh-szepeviktor]: https://github.com/szepeviktor -[gh-TimothyBJacobs]: https://github.com/TimothyBJacobs -[gh-tnorthcutt]: https://github.com/tnorthcutt -[gh-TobiasBg]: https://github.com/TobiasBg -[gh-todeveni]: https://github.com/todeveni -[gh-tomsommer]: https://github.com/tomsommer -[gh-tonebender]: https://github.com/tonebender -[gh-twdnhfr]: https://github.com/twdnhfr -[gh-TysonAndre]: https://github.com/TysonAndre -[gh-whyisjake]: https://github.com/whyisjake -[gh-wojsmol]: https://github.com/wojsmol -[gh-xknown]: https://github.com/xknown -[gh-Zegnat]: https://github.com/Zegnat -[gh-ZsgsDesign]: https://github.com/ZsgsDesign - diff --git a/bundle/rmccue/requests/LICENSE b/bundle/rmccue/requests/LICENSE deleted file mode 100644 index d61ae7b14..000000000 --- a/bundle/rmccue/requests/LICENSE +++ /dev/null @@ -1,49 +0,0 @@ -Requests -======== - -Copyright (c) 2010-2012 Ryan McCue and contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - -ComplexPie IRI Parser -===================== - -Copyright (c) 2007-2010, Geoffrey Sneddon and Steve Minutillo. -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. - - * Neither the name of the SimplePie Team nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -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 HOLDERS AND 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. diff --git a/bundle/rmccue/requests/README.md b/bundle/rmccue/requests/README.md deleted file mode 100644 index 70d06617e..000000000 --- a/bundle/rmccue/requests/README.md +++ /dev/null @@ -1,187 +0,0 @@ -Requests for PHP -================ - -[![CS](https://github.com/WordPress/Requests/actions/workflows/cs.yml/badge.svg)](https://github.com/WordPress/Requests/actions/workflows/cs.yml) -[![Lint](https://github.com/WordPress/Requests/actions/workflows/lint.yml/badge.svg)](https://github.com/WordPress/Requests/actions/workflows/lint.yml) -[![Test](https://github.com/WordPress/Requests/actions/workflows/test.yml/badge.svg)](https://github.com/WordPress/Requests/actions/workflows/test.yml) -[![codecov.io](https://codecov.io/gh/WordPress/Requests/branch/stable/graph/badge.svg?token=AfpxK7WMxj&branch=stable)](https://codecov.io/gh/WordPress/Requests?branch=stable) -[![Packagist License](https://img.shields.io/packagist/l/rmccue/requests)](https://github.com/WordPress/Requests/blob/stable/LICENSE) - -Requests is a HTTP library written in PHP, for human beings. It is roughly -based on the API from the excellent [Requests Python -library](https://requests.readthedocs.io/en/latest/). Requests is [ISC -Licensed](https://github.com/WordPress/Requests/blob/stable/LICENSE) (similar to -the new BSD license) and has no dependencies, except for PHP 5.6+. - -Despite PHP's use as a language for the web, its tools for sending HTTP requests -are severely lacking. cURL has an -[interesting API](https://www.php.net/curl-setopt), to say the -least, and you can't always rely on it being available. Sockets provide only low -level access, and require you to build most of the HTTP response parsing -yourself. - -We all have better things to do. That's why Requests was born. - -```php -$headers = array('Accept' => 'application/json'); -$options = array('auth' => array('user', 'pass')); -$request = WpOrg\Requests\Requests::get('https://api.github.com/gists', $headers, $options); - -var_dump($request->status_code); -// int(200) - -var_dump($request->headers['content-type']); -// string(31) "application/json; charset=utf-8" - -var_dump($request->body); -// string(26891) "[...]" -``` - -Requests allows you to send **HEAD**, **GET**, **POST**, **PUT**, **DELETE**, -and **PATCH** HTTP requests. You can add headers, form data, multipart files, -and parameters with basic arrays, and access the response data in the same way. -Requests uses cURL and fsockopen, depending on what your system has available, -but abstracts all the nasty stuff out of your way, providing a consistent API. - - -Features --------- - -- International Domains and URLs -- Browser-style SSL Verification -- Basic/Digest Authentication -- Automatic Decompression -- Connection Timeouts - - -Installation ------------- - -### Install with Composer -If you're using [Composer](https://getcomposer.org/) to manage -dependencies, you can add Requests with it. - -```sh -composer require rmccue/requests -``` - -or -```json -{ - "require": { - "rmccue/requests": "^2.0" - } -} -``` - -### Install source from GitHub -To install the source code: -```bash -$ git clone git://github.com/WordPress/Requests.git -``` - -Next, include the autoloader in your scripts: -```php -require_once '/path/to/Requests/src/Autoload.php'; -``` - -You'll probably also want to register the autoloader: -```php -WpOrg\Requests\Autoload::register(); -``` - -### Install source from zip/tarball -Alternatively, you can fetch a [tarball][] or [zipball][]: - -```bash -$ curl -L https://github.com/WordPress/Requests/tarball/stable | tar xzv -(or) -$ wget https://github.com/WordPress/Requests/tarball/stable -O - | tar xzv -``` - -[tarball]: https://github.com/WordPress/Requests/tarball/stable -[zipball]: https://github.com/WordPress/Requests/zipball/stable - - -### Using a Class Loader -If you're using a class loader (e.g., [Symfony Class Loader][]) for -[PSR-4][]-style class loading: -```php -$loader = new Psr4ClassLoader(); -$loader->addPrefix('WpOrg\\Requests\\', 'path/to/vendor/Requests/src'); -$loader->register(); -``` - -[Symfony Class Loader]: https://github.com/symfony/ClassLoader -[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4.md - - -Documentation -------------- -The best place to start is our [prose-based documentation][], which will guide -you through using Requests. - -After that, take a look at [the documentation for -`\WpOrg\Requests\Requests::request()`][request_method], where all the parameters are fully -documented. - -Requests is [100% documented with PHPDoc](https://requests.ryanmccue.info/api-2.x/). -If you find any problems with it, [create a new -issue](https://github.com/WordPress/Requests/issues/new)! - -[prose-based documentation]: https://github.com/WordPress/Requests/blob/stable/docs/README.md -[request_method]: https://requests.ryanmccue.info/api-2.x/classes/WpOrg-Requests-Requests.html#method_request - -Testing -------- - -Requests strives to have 100% code-coverage of the library with an extensive -set of tests. We're not quite there yet, but [we're getting close][codecov]. - -[codecov]: https://codecov.io/github/WordPress/Requests/ - -To run the test suite, first check that you have the [PHP -JSON extension ](https://www.php.net/book.json) enabled. Then -simply: -```bash -$ phpunit -``` - -If you'd like to run a single set of tests, specify just the name: -```bash -$ phpunit Transport/cURL -``` - -Requests and PSR-7/PSR-18 -------------------------- - -[PSR-7][psr-7] describes common interfaces for representing HTTP messages. -[PSR-18][psr-18] describes a common interface for sending HTTP requests and receiving HTTP responses. - -Both PSR-7 as well as PSR-18 were created after Requests' conception. -At this time, there is no intention to add a native PSR-7/PSR-18 implementation to the Requests library. - -However, the amazing [Artur Weigandt][art4] has created a [package][requests-psr-18], which allows you to use Requests as a PSR-7 compatible PSR-18 HTTP Client. -If you are interested in a PSR-7/PSR-18 compatible version of Requests, we highly recommend you check out [this package][requests-psr-18]. - -[psr-7]: https://www.php-fig.org/psr/psr-7/ -[psr-18]: https://www.php-fig.org/psr/psr-18/ -[art4]: https://github.com/Art4 -[requests-psr-18]: https://packagist.org/packages/art4/requests-psr18-adapter - - -Contribute ----------- - -1. Check for open issues or open a new issue for a feature request or a bug. -2. Fork [the repository][] on Github to start making your changes to the - `develop` branch (or branch off of it). -3. Write one or more tests which show that the bug was fixed or that the feature works as expected. -4. Send in a pull request. - -If you have questions while working on your contribution and you use Slack, there is -a [#core-http-api] channel available in the [WordPress Slack] in which contributions can be discussed. - -[the repository]: https://github.com/WordPress/Requests -[#core-http-api]: https://wordpress.slack.com/archives/C02BBE29V42 -[WordPress Slack]: https://make.wordpress.org/chat/ diff --git a/bundle/rmccue/requests/certificates/cacert.pem b/bundle/rmccue/requests/certificates/cacert.pem deleted file mode 100644 index a78e1dd47..000000000 --- a/bundle/rmccue/requests/certificates/cacert.pem +++ /dev/null @@ -1,3529 +0,0 @@ -## -## Bundle of CA Root Certificates -## -## Certificate data from Mozilla last updated on: Wed Feb 11 18:26:30 2026 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.32. -## SHA256: 3b98d4e3ff57a326d9587c33633039c8c3a9cf0b55f7ca581d7598ff329eb1f3 -## - - -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----- - -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----- - -TrustAsia TLS ECC Root CA -========================= ------BEGIN CERTIFICATE----- -MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkGA1UE -BhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMTGVRy -dXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBY -MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAG -A1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/ -pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDpguMqWzJ8 -S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49 -BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15K -eAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== ------END CERTIFICATE----- - -TrustAsia TLS RSA Root CA -========================= ------BEGIN CERTIFICATE----- -MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDELMAkG -A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMT -GVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2 -WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEi -MCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+NmDQDIPN -lOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJQ1DNDX3eRA5gEk9bNb2/ -mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fk -zv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYo -zza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyr -z2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoy -IBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6C -FabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1 -J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnT -q1mt1tve1CuBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZ -ylomkadFK/hTMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 -Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8 -veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L -TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHx -tlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp -27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87q -qA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongPXvPKnbwb -PKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIweSsCI3zWQzj8C9GRh3sfI -B5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNz -FrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNddBWcwauwxzQ= ------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----- - -SwissSign RSA TLS Root CA 2022 - 1 -================================== ------BEGIN CERTIFICATE----- -MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTELMAkG -A1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWduIFJTQSBU -TFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJ -BgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0Eg -VExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmji -C8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7LCTLf5Im -gKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX5XH8irCRIFucdFJtrhUn -WXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyEEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlf -GUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36q -OTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLO -EGrOyvi5KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPl -EuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OC -eYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3 -Q4IgaezprClG5QtO+DdziZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQAB -o2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow -4UD2p8P98Q+4DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL -BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6W -lkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo -8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zp -y1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3Cjlvr -zG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6M -OuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJpzv1/THfQ -wUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/TdAo9QAwKxuDdollDruF/U -KIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0n -hzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rw -tnu64ZzZ ------END CERTIFICATE----- - -OISTE Server Root ECC G1 -======================== ------BEGIN CERTIFICATE----- -MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQswCQYDVQQGEwJD -SDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJvb3Qg -RUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUyNDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAX -BgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBH -MTB2MBAGByqGSM49AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOuj -vqQycvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N2xml4z+c -KrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3TYhlz/w9itWj8UnATgwQ -b0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9CtJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqG -SM49BAMDA2kAMGYCMQCpKjAd0MKfkFFRQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxg -ZzFDJe0CMQCSia7pXGKDYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= ------END CERTIFICATE----- - -OISTE Server Root RSA G1 -======================== ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYDVQQG -EwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJv -b3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gx -GTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJT -QSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxV -YOPMvLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7brEi56rAU -jtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzkik/HEzxux9UTl7Ko2yRp -g1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4zO8vbUZeUapU8zhhabkvG/AePLhq5Svdk -NCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8RtOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY -+m0o/DjH40ytas7ZTpOSjswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+ -lKXHiHUhsd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+HomnqT -8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu+zrkL8Fl47l6QGzw -Brd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYRi3drVByjtdgQ8K4p92cIiBdcuJd5 -z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnTkCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQF -MAMBAf8wHwYDVR0jBBgwFoAU8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC7 -7EUOSh+1sbM2zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 -I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG5D1rd9QhEOP2 -8yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8qyiWXmFcuCIzGEgWUOrKL+ml -Sdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dPAGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l -8PjaV8GUgeV6Vg27Rn9vkf195hfkgSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+ -FKrDgHGdPY3ofRRsYWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNq -qYY19tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome/msVuduC -msuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3J8tRd/iWkx7P8nd9H0aT -olkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2wq1yVAb+axj5d9spLFKebXd7Yv0PTY6Y -MjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DOReIoMt0r7+hVu05L0= ------END CERTIFICATE----- - -e-Szigno TLS Root CA 2023 -========================= ------BEGIN CERTIFICATE----- -MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJIVTER -MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhV -LTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0 -MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYw -FAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZ -ZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAGgP36J8 -PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFSAL/fjO1ZrTJlqwlZULUZ -wmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/vSzUaQ49CE0y5LBqcvjC2xN7cS53kpDzL -Ltmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E -FgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0Uw -CgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve -7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIw -wzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6uWWL ------END CERTIFICATE----- diff --git a/bundle/rmccue/requests/certificates/cacert.pem.sha256 b/bundle/rmccue/requests/certificates/cacert.pem.sha256 deleted file mode 100644 index ecd2e5fdd..000000000 --- a/bundle/rmccue/requests/certificates/cacert.pem.sha256 +++ /dev/null @@ -1 +0,0 @@ -b6e66569cc3d438dd5abe514d0df50005d570bfc96c14dca8f768d020cb96171 cacert.pem diff --git a/bundle/rmccue/requests/composer.json b/bundle/rmccue/requests/composer.json deleted file mode 100644 index da9a903fc..000000000 --- a/bundle/rmccue/requests/composer.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "name": "rmccue/requests", - "description": "A HTTP library written in PHP, for human beings.", - "homepage": "https://requests.ryanmccue.info/", - "license": "ISC", - "type": "library", - "keywords": [ - "http", - "idna", - "iri", - "ipv6", - "curl", - "sockets", - "fsockopen" - ], - "authors": [ - { - "name": "Ryan McCue", - "homepage": "https://rmccue.io/" - }, - { - "name": "Alain Schlesser", - "homepage": "https://github.com/schlessera" - }, - { - "name": "Juliette Reinders Folmer", - "homepage": "https://github.com/jrfnl" - }, - { - "name": "Contributors", - "homepage": "https://github.com/WordPress/Requests/graphs/contributors" - } - ], - "support": { - "issues": "https://github.com/WordPress/Requests/issues", - "source": "https://github.com/WordPress/Requests", - "docs": "https://requests.ryanmccue.info/" - }, - "require": { - "php": ">=5.6", - "ext-json": "*" - }, - "config": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - }, - "lock": false - }, - "require-dev": { - "requests/test-server": "dev-main", - "squizlabs/php_codesniffer": "^3.6", - "phpcompatibility/php-compatibility": "^10.0.0@dev", - "wp-coding-standards/wpcs": "^2.0", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3.1", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "yoast/phpunit-polyfills": "^1.1.5" - }, - "suggest": { - "ext-curl": "For improved performance", - "ext-openssl": "For secure transport support", - "ext-zlib": "For improved performance when decompressing encoded streams", - "art4/requests-psr18-adapter": "For using Requests as a PSR-18 HTTP Client" - }, - "minimum-stability": "dev", - "prefer-stable": true, - "autoload": { - "psr-4": { - "WpOrg\\Requests\\": "src/" - }, - "classmap": ["library/Requests.php"], - "files": ["library/Deprecated.php"] - }, - "autoload-dev": { - "psr-4": { - "WpOrg\\Requests\\Tests\\": "tests/" - } - }, - "scripts": { - "lint": [ - "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --exclude vendor --exclude .git" - ], - "checkcs": [ - "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs" - ], - "fixcs": [ - "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf" - ], - "test": [ - "@php ./vendor/phpunit/phpunit/phpunit --no-coverage" - ], - "coverage": [ - "@php ./vendor/phpunit/phpunit/phpunit" - ] - } -} diff --git a/bundle/rmccue/requests/library/Deprecated.php b/bundle/rmccue/requests/library/Deprecated.php deleted file mode 100644 index 471eed57e..000000000 --- a/bundle/rmccue/requests/library/Deprecated.php +++ /dev/null @@ -1,19 +0,0 @@ -user, $this->pass) = $args; - return; - } - - if ($args !== null) { - throw InvalidArgument::create(1, '$args', 'array|null', gettype($args)); - } - } - - /** - * Register the necessary callbacks - * - * @see \WpOrg\Requests\Auth\Basic::curl_before_send() - * @see \WpOrg\Requests\Auth\Basic::fsockopen_header() - * @param \WpOrg\Requests\Hooks $hooks Hook system - */ - public function register(Hooks $hooks) { - $hooks->register('curl.before_send', [$this, 'curl_before_send']); - $hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); - } - - /** - * Set cURL parameters before the data is sent - * - * @param resource|\CurlHandle $handle cURL handle - */ - public function curl_before_send(&$handle) { - curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString()); - } - - /** - * Add extra headers to the request before sending - * - * @param string $out HTTP header string - */ - public function fsockopen_header(&$out) { - $out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString())); - } - - /** - * Get the authentication string (user:pass) - * - * @return string - */ - public function getAuthString() { - return $this->user . ':' . $this->pass; - } -} diff --git a/bundle/rmccue/requests/src/Autoload.php b/bundle/rmccue/requests/src/Autoload.php deleted file mode 100644 index 669ddecaf..000000000 --- a/bundle/rmccue/requests/src/Autoload.php +++ /dev/null @@ -1,187 +0,0 @@ - '\WpOrg\Requests\Auth', - 'requests_hooker' => '\WpOrg\Requests\HookManager', - 'requests_proxy' => '\WpOrg\Requests\Proxy', - 'requests_transport' => '\WpOrg\Requests\Transport', - - // Classes. - 'requests_cookie' => '\WpOrg\Requests\Cookie', - 'requests_exception' => '\WpOrg\Requests\Exception', - 'requests_hooks' => '\WpOrg\Requests\Hooks', - 'requests_idnaencoder' => '\WpOrg\Requests\IdnaEncoder', - 'requests_ipv6' => '\WpOrg\Requests\Ipv6', - 'requests_iri' => '\WpOrg\Requests\Iri', - 'requests_response' => '\WpOrg\Requests\Response', - 'requests_session' => '\WpOrg\Requests\Session', - 'requests_ssl' => '\WpOrg\Requests\Ssl', - 'requests_auth_basic' => '\WpOrg\Requests\Auth\Basic', - 'requests_cookie_jar' => '\WpOrg\Requests\Cookie\Jar', - 'requests_proxy_http' => '\WpOrg\Requests\Proxy\Http', - 'requests_response_headers' => '\WpOrg\Requests\Response\Headers', - 'requests_transport_curl' => '\WpOrg\Requests\Transport\Curl', - 'requests_transport_fsockopen' => '\WpOrg\Requests\Transport\Fsockopen', - 'requests_utility_caseinsensitivedictionary' => '\WpOrg\Requests\Utility\CaseInsensitiveDictionary', - 'requests_utility_filterediterator' => '\WpOrg\Requests\Utility\FilteredIterator', - 'requests_exception_http' => '\WpOrg\Requests\Exception\Http', - 'requests_exception_transport' => '\WpOrg\Requests\Exception\Transport', - 'requests_exception_transport_curl' => '\WpOrg\Requests\Exception\Transport\Curl', - 'requests_exception_http_304' => '\WpOrg\Requests\Exception\Http\Status304', - 'requests_exception_http_305' => '\WpOrg\Requests\Exception\Http\Status305', - 'requests_exception_http_306' => '\WpOrg\Requests\Exception\Http\Status306', - 'requests_exception_http_400' => '\WpOrg\Requests\Exception\Http\Status400', - 'requests_exception_http_401' => '\WpOrg\Requests\Exception\Http\Status401', - 'requests_exception_http_402' => '\WpOrg\Requests\Exception\Http\Status402', - 'requests_exception_http_403' => '\WpOrg\Requests\Exception\Http\Status403', - 'requests_exception_http_404' => '\WpOrg\Requests\Exception\Http\Status404', - 'requests_exception_http_405' => '\WpOrg\Requests\Exception\Http\Status405', - 'requests_exception_http_406' => '\WpOrg\Requests\Exception\Http\Status406', - 'requests_exception_http_407' => '\WpOrg\Requests\Exception\Http\Status407', - 'requests_exception_http_408' => '\WpOrg\Requests\Exception\Http\Status408', - 'requests_exception_http_409' => '\WpOrg\Requests\Exception\Http\Status409', - 'requests_exception_http_410' => '\WpOrg\Requests\Exception\Http\Status410', - 'requests_exception_http_411' => '\WpOrg\Requests\Exception\Http\Status411', - 'requests_exception_http_412' => '\WpOrg\Requests\Exception\Http\Status412', - 'requests_exception_http_413' => '\WpOrg\Requests\Exception\Http\Status413', - 'requests_exception_http_414' => '\WpOrg\Requests\Exception\Http\Status414', - 'requests_exception_http_415' => '\WpOrg\Requests\Exception\Http\Status415', - 'requests_exception_http_416' => '\WpOrg\Requests\Exception\Http\Status416', - 'requests_exception_http_417' => '\WpOrg\Requests\Exception\Http\Status417', - 'requests_exception_http_418' => '\WpOrg\Requests\Exception\Http\Status418', - 'requests_exception_http_428' => '\WpOrg\Requests\Exception\Http\Status428', - 'requests_exception_http_429' => '\WpOrg\Requests\Exception\Http\Status429', - 'requests_exception_http_431' => '\WpOrg\Requests\Exception\Http\Status431', - 'requests_exception_http_500' => '\WpOrg\Requests\Exception\Http\Status500', - 'requests_exception_http_501' => '\WpOrg\Requests\Exception\Http\Status501', - 'requests_exception_http_502' => '\WpOrg\Requests\Exception\Http\Status502', - 'requests_exception_http_503' => '\WpOrg\Requests\Exception\Http\Status503', - 'requests_exception_http_504' => '\WpOrg\Requests\Exception\Http\Status504', - 'requests_exception_http_505' => '\WpOrg\Requests\Exception\Http\Status505', - 'requests_exception_http_511' => '\WpOrg\Requests\Exception\Http\Status511', - 'requests_exception_http_unknown' => '\WpOrg\Requests\Exception\Http\StatusUnknown', - ]; - - /** - * Register the autoloader. - * - * Note: the autoloader is *prepended* in the autoload queue. - * This is done to ensure that the Requests 2.0 autoloader takes precedence - * over a potentially (dependency-registered) Requests 1.x autoloader. - * - * @internal This method contains a safeguard against the autoloader being - * registered multiple times. This safeguard uses a global constant to - * (hopefully/in most cases) still function correctly, even if the - * class would be renamed. - * - * @return void - */ - public static function register() { - if (defined('REQUESTS_AUTOLOAD_REGISTERED') === false) { - spl_autoload_register([self::class, 'load'], true); - define('REQUESTS_AUTOLOAD_REGISTERED', true); - } - } - - /** - * Autoloader. - * - * @param string $class_name Name of the class name to load. - * - * @return bool Whether a class was loaded or not. - */ - public static function load($class_name) { - // Check that the class starts with "Requests" (PSR-0) or "WpOrg\Requests" (PSR-4). - $psr_4_prefix_pos = strpos($class_name, 'WpOrg\\Requests\\'); - - if (stripos($class_name, 'Requests') !== 0 && $psr_4_prefix_pos !== 0) { - return false; - } - - $class_lower = strtolower($class_name); - - if ($class_lower === 'requests') { - // Reference to the original PSR-0 Requests class. - $file = dirname(__DIR__) . '/library/Requests.php'; - } elseif ($psr_4_prefix_pos === 0) { - // PSR-4 classname. - $file = __DIR__ . '/' . strtr(substr($class_name, 15), '\\', '/') . '.php'; - } - - if (isset($file) && file_exists($file)) { - include $file; - return true; - } - - /* - * Okay, so the class starts with "Requests", but we couldn't find the file. - * If this is one of the deprecated/renamed PSR-0 classes being requested, - * let's alias it to the new name and throw a deprecation notice. - */ - if (isset(self::$deprecated_classes[$class_lower])) { - /* - * Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations - * by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`. - * The constant needs to be defined before the first deprecated class is requested - * via this autoloader. - */ - if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== true) { - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error - trigger_error( - 'The PSR-0 `Requests_...` class names in the Requests library are deprecated.' - . ' Switch to the PSR-4 `WpOrg\Requests\...` class names at your earliest convenience.', - E_USER_DEPRECATED - ); - - // Prevent the deprecation notice from being thrown twice. - if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) { - define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true); - } - } - - // Create an alias and let the autoloader recursively kick in to load the PSR-4 class. - return class_alias(self::$deprecated_classes[$class_lower], $class_name, true); - } - - return false; - } - } -} diff --git a/bundle/rmccue/requests/src/Capability.php b/bundle/rmccue/requests/src/Capability.php deleted file mode 100644 index 30572bab2..000000000 --- a/bundle/rmccue/requests/src/Capability.php +++ /dev/null @@ -1,36 +0,0 @@ -name = $name; - $this->value = $value; - $this->attributes = $attributes; - $default_flags = [ - 'creation' => time(), - 'last-access' => time(), - 'persistent' => false, - 'host-only' => true, - ]; - $this->flags = array_merge($default_flags, $flags); - - $this->reference_time = time(); - if ($reference_time !== null) { - $this->reference_time = $reference_time; - } - - $this->normalize(); - } - - /** - * Get the cookie value - * - * Attributes and other data can be accessed via methods. - */ - public function __toString() { - return $this->value; - } - - /** - * Check if a cookie is expired. - * - * Checks the age against $this->reference_time to determine if the cookie - * is expired. - * - * @return boolean True if expired, false if time is valid. - */ - public function is_expired() { - // RFC6265, s. 4.1.2.2: - // If a cookie has both the Max-Age and the Expires attribute, the Max- - // Age attribute has precedence and controls the expiration date of the - // cookie. - if (isset($this->attributes['max-age'])) { - $max_age = $this->attributes['max-age']; - return $max_age < $this->reference_time; - } - - if (isset($this->attributes['expires'])) { - $expires = $this->attributes['expires']; - return $expires < $this->reference_time; - } - - return false; - } - - /** - * Check if a cookie is valid for a given URI - * - * @param \WpOrg\Requests\Iri $uri URI to check - * @return boolean Whether the cookie is valid for the given URI - */ - public function uri_matches(Iri $uri) { - if (!$this->domain_matches($uri->host)) { - return false; - } - - if (!$this->path_matches($uri->path)) { - return false; - } - - return empty($this->attributes['secure']) || $uri->scheme === 'https'; - } - - /** - * Check if a cookie is valid for a given domain - * - * @param string $domain Domain to check - * @return boolean Whether the cookie is valid for the given domain - */ - public function domain_matches($domain) { - if (is_string($domain) === false) { - return false; - } - - if (!isset($this->attributes['domain'])) { - // Cookies created manually; cookies created by Requests will set - // the domain to the requested domain - return true; - } - - $cookie_domain = $this->attributes['domain']; - if ($cookie_domain === $domain) { - // The cookie domain and the passed domain are identical. - return true; - } - - // If the cookie is marked as host-only and we don't have an exact - // match, reject the cookie - if ($this->flags['host-only'] === true) { - return false; - } - - if (strlen($domain) <= strlen($cookie_domain)) { - // For obvious reasons, the cookie domain cannot be a suffix if the passed domain - // is shorter than the cookie domain - return false; - } - - if (substr($domain, -1 * strlen($cookie_domain)) !== $cookie_domain) { - // The cookie domain should be a suffix of the passed domain. - return false; - } - - $prefix = substr($domain, 0, strlen($domain) - strlen($cookie_domain)); - if (substr($prefix, -1) !== '.') { - // The last character of the passed domain that is not included in the - // domain string should be a %x2E (".") character. - return false; - } - - // The passed domain should be a host name (i.e., not an IP address). - return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $domain); - } - - /** - * Check if a cookie is valid for a given path - * - * From the path-match check in RFC 6265 section 5.1.4 - * - * @param string $request_path Path to check - * @return boolean Whether the cookie is valid for the given path - */ - public function path_matches($request_path) { - if (empty($request_path)) { - // Normalize empty path to root - $request_path = '/'; - } - - if (!isset($this->attributes['path'])) { - // Cookies created manually; cookies created by Requests will set - // the path to the requested path - return true; - } - - if (is_scalar($request_path) === false) { - return false; - } - - $cookie_path = $this->attributes['path']; - - if ($cookie_path === $request_path) { - // The cookie-path and the request-path are identical. - return true; - } - - if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) { - if (substr($cookie_path, -1) === '/') { - // The cookie-path is a prefix of the request-path, and the last - // character of the cookie-path is %x2F ("/"). - return true; - } - - if (substr($request_path, strlen($cookie_path), 1) === '/') { - // The cookie-path is a prefix of the request-path, and the - // first character of the request-path that is not included in - // the cookie-path is a %x2F ("/") character. - return true; - } - } - - return false; - } - - /** - * Normalize cookie and attributes - * - * @return boolean Whether the cookie was successfully normalized - */ - public function normalize() { - foreach ($this->attributes as $key => $value) { - $orig_value = $value; - - if (is_string($key)) { - $value = $this->normalize_attribute($key, $value); - } - - if ($value === null) { - unset($this->attributes[$key]); - continue; - } - - if ($value !== $orig_value) { - $this->attributes[$key] = $value; - } - } - - return true; - } - - /** - * Parse an individual cookie attribute - * - * Handles parsing individual attributes from the cookie values. - * - * @param string $name Attribute name - * @param string|int|bool $value Attribute value (string/integer value, or true if empty/flag) - * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped) - */ - protected function normalize_attribute($name, $value) { - switch (strtolower($name)) { - case 'expires': - // Expiration parsing, as per RFC 6265 section 5.2.1 - if (is_int($value)) { - return $value; - } - - $expiry_time = strtotime($value); - if ($expiry_time === false) { - return null; - } - - return $expiry_time; - - case 'max-age': - // Expiration parsing, as per RFC 6265 section 5.2.2 - if (is_int($value)) { - return $value; - } - - // Check that we have a valid age - if (!preg_match('/^-?\d+$/', $value)) { - return null; - } - - $delta_seconds = (int) $value; - if ($delta_seconds <= 0) { - $expiry_time = 0; - } else { - $expiry_time = $this->reference_time + $delta_seconds; - } - - return $expiry_time; - - case 'domain': - // Domains are not required as per RFC 6265 section 5.2.3 - if (empty($value)) { - return null; - } - - // Domain normalization, as per RFC 6265 section 5.2.3 - if ($value[0] === '.') { - $value = substr($value, 1); - } - - return $value; - - default: - return $value; - } - } - - /** - * Format a cookie for a Cookie header - * - * This is used when sending cookies to a server. - * - * @return string Cookie formatted for Cookie header - */ - public function format_for_header() { - return sprintf('%s=%s', $this->name, $this->value); - } - - /** - * Format a cookie for a Set-Cookie header - * - * This is used when sending cookies to clients. This isn't really - * applicable to client-side usage, but might be handy for debugging. - * - * @return string Cookie formatted for Set-Cookie header - */ - public function format_for_set_cookie() { - $header_value = $this->format_for_header(); - if (!empty($this->attributes)) { - $parts = []; - foreach ($this->attributes as $key => $value) { - // Ignore non-associative attributes - if (is_numeric($key)) { - $parts[] = $value; - } else { - $parts[] = sprintf('%s=%s', $key, $value); - } - } - - $header_value .= '; ' . implode('; ', $parts); - } - - return $header_value; - } - - /** - * Parse a cookie string into a cookie object - * - * Based on Mozilla's parsing code in Firefox and related projects, which - * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 - * specifies some of this handling, but not in a thorough manner. - * - * @param string $cookie_header Cookie header value (from a Set-Cookie header) - * @param string $name - * @param int|null $reference_time - * @return \WpOrg\Requests\Cookie Parsed cookie object - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cookie_header argument is not a string. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string. - */ - public static function parse($cookie_header, $name = '', $reference_time = null) { - if (is_string($cookie_header) === false) { - throw InvalidArgument::create(1, '$cookie_header', 'string', gettype($cookie_header)); - } - - if (is_string($name) === false) { - throw InvalidArgument::create(2, '$name', 'string', gettype($name)); - } - - $parts = explode(';', $cookie_header); - $kvparts = array_shift($parts); - - if (!empty($name)) { - $value = $cookie_header; - } elseif (strpos($kvparts, '=') === false) { - // Some sites might only have a value without the equals separator. - // Deviate from RFC 6265 and pretend it was actually a blank name - // (`=foo`) - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=169091 - $name = ''; - $value = $kvparts; - } else { - list($name, $value) = explode('=', $kvparts, 2); - } - - $name = trim($name); - $value = trim($value); - - // Attribute keys are handled case-insensitively - $attributes = new CaseInsensitiveDictionary(); - - if (!empty($parts)) { - foreach ($parts as $part) { - if (strpos($part, '=') === false) { - $part_key = $part; - $part_value = true; - } else { - list($part_key, $part_value) = explode('=', $part, 2); - $part_value = trim($part_value); - } - - $part_key = trim($part_key); - $attributes[$part_key] = $part_value; - } - } - - return new static($name, $value, $attributes, [], $reference_time); - } - - /** - * Parse all Set-Cookie headers from request headers - * - * @param \WpOrg\Requests\Response\Headers $headers Headers to parse from - * @param \WpOrg\Requests\Iri|null $origin URI for comparing cookie origins - * @param int|null $time Reference time for expiration calculation - * @return array - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $origin argument is not null or an instance of the Iri class. - */ - public static function parse_from_headers(Headers $headers, $origin = null, $time = null) { - $cookie_headers = $headers->getValues('Set-Cookie'); - if (empty($cookie_headers)) { - return []; - } - - if ($origin !== null && !($origin instanceof Iri)) { - throw InvalidArgument::create(2, '$origin', Iri::class . ' or null', gettype($origin)); - } - - $cookies = []; - foreach ($cookie_headers as $header) { - $parsed = self::parse($header, '', $time); - - // Default domain/path attributes - if (empty($parsed->attributes['domain']) && !empty($origin)) { - $parsed->attributes['domain'] = $origin->host; - $parsed->flags['host-only'] = true; - } else { - $parsed->flags['host-only'] = false; - } - - $path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/'); - if (!$path_is_valid && !empty($origin)) { - $path = $origin->path; - - // Default path normalization as per RFC 6265 section 5.1.4 - if (substr($path, 0, 1) !== '/') { - // If the uri-path is empty or if the first character of - // the uri-path is not a %x2F ("/") character, output - // %x2F ("/") and skip the remaining steps. - $path = '/'; - } elseif (substr_count($path, '/') === 1) { - // If the uri-path contains no more than one %x2F ("/") - // character, output %x2F ("/") and skip the remaining - // step. - $path = '/'; - } else { - // Output the characters of the uri-path from the first - // character up to, but not including, the right-most - // %x2F ("/"). - $path = substr($path, 0, strrpos($path, '/')); - } - - $parsed->attributes['path'] = $path; - } - - // Reject invalid cookie domains - if (!empty($origin) && !$parsed->domain_matches($origin->host)) { - continue; - } - - $cookies[$parsed->name] = $parsed; - } - - return $cookies; - } -} diff --git a/bundle/rmccue/requests/src/Cookie/Jar.php b/bundle/rmccue/requests/src/Cookie/Jar.php deleted file mode 100644 index 7633786b9..000000000 --- a/bundle/rmccue/requests/src/Cookie/Jar.php +++ /dev/null @@ -1,187 +0,0 @@ -cookies = $cookies; - } - - /** - * Normalise cookie data into a \WpOrg\Requests\Cookie - * - * @param string|\WpOrg\Requests\Cookie $cookie Cookie header value, possibly pre-parsed (object). - * @param string $key Optional. The name for this cookie. - * @return \WpOrg\Requests\Cookie - */ - public function normalize_cookie($cookie, $key = '') { - if ($cookie instanceof Cookie) { - return $cookie; - } - - return Cookie::parse($cookie, $key); - } - - /** - * Check if the given item exists - * - * @param string $offset Item key - * @return boolean Does the item exist? - */ - #[ReturnTypeWillChange] - public function offsetExists($offset) { - return isset($this->cookies[$offset]); - } - - /** - * Get the value for the item - * - * @param string $offset Item key - * @return string|null Item value (null if offsetExists is false) - */ - #[ReturnTypeWillChange] - public function offsetGet($offset) { - if (!isset($this->cookies[$offset])) { - return null; - } - - return $this->cookies[$offset]; - } - - /** - * Set the given item - * - * @param string $offset Item name - * @param string $value Item value - * - * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) - */ - #[ReturnTypeWillChange] - public function offsetSet($offset, $value) { - if ($offset === null) { - throw new Exception('Object is a dictionary, not a list', 'invalidset'); - } - - $this->cookies[$offset] = $value; - } - - /** - * Unset the given header - * - * @param string $offset The key for the item to unset. - */ - #[ReturnTypeWillChange] - public function offsetUnset($offset) { - unset($this->cookies[$offset]); - } - - /** - * Get an iterator for the data - * - * @return \ArrayIterator - */ - #[ReturnTypeWillChange] - public function getIterator() { - return new ArrayIterator($this->cookies); - } - - /** - * Register the cookie handler with the request's hooking system - * - * @param \WpOrg\Requests\HookManager $hooks Hooking system - */ - public function register(HookManager $hooks) { - $hooks->register('requests.before_request', [$this, 'before_request']); - $hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']); - } - - /** - * Add Cookie header to a request if we have any - * - * As per RFC 6265, cookies are separated by '; ' - * - * @param string $url - * @param array $headers - * @param array $data - * @param string $type - * @param array $options - */ - public function before_request($url, &$headers, &$data, &$type, &$options) { - if (!$url instanceof Iri) { - $url = new Iri($url); - } - - if (!empty($this->cookies)) { - $cookies = []; - foreach ($this->cookies as $key => $cookie) { - $cookie = $this->normalize_cookie($cookie, $key); - - // Skip expired cookies - if ($cookie->is_expired()) { - continue; - } - - if ($cookie->domain_matches($url->host)) { - $cookies[] = $cookie->format_for_header(); - } - } - - $headers['Cookie'] = implode('; ', $cookies); - } - } - - /** - * Parse all cookies from a response and attach them to the response - * - * @param \WpOrg\Requests\Response $response Response as received. - */ - public function before_redirect_check(Response $response) { - $url = $response->url; - if (!$url instanceof Iri) { - $url = new Iri($url); - } - - $cookies = Cookie::parse_from_headers($response->headers, $url); - $this->cookies = array_merge($this->cookies, $cookies); - $response->cookies = $this; - } -} diff --git a/bundle/rmccue/requests/src/Exception.php b/bundle/rmccue/requests/src/Exception.php deleted file mode 100644 index b67d1b1a4..000000000 --- a/bundle/rmccue/requests/src/Exception.php +++ /dev/null @@ -1,66 +0,0 @@ -type = $type; - $this->data = $data; - } - - /** - * Like {@see \Exception::getCode()}, but a string code. - * - * @codeCoverageIgnore - * @return string - */ - public function getType() { - return $this->type; - } - - /** - * Gives any relevant data - * - * @codeCoverageIgnore - * @return mixed - */ - public function getData() { - return $this->data; - } -} diff --git a/bundle/rmccue/requests/src/Exception/ArgumentCount.php b/bundle/rmccue/requests/src/Exception/ArgumentCount.php deleted file mode 100644 index b5773ddf3..000000000 --- a/bundle/rmccue/requests/src/Exception/ArgumentCount.php +++ /dev/null @@ -1,47 +0,0 @@ -reason = $reason; - } - - $message = sprintf('%d %s', $this->code, $this->reason); - parent::__construct($message, 'httpresponse', $data, $this->code); - } - - /** - * Get the status message. - * - * @return string - */ - public function getReason() { - return $this->reason; - } - - /** - * Get the correct exception class for a given error code - * - * @param int|bool $code HTTP status code, or false if unavailable - * @return string Exception class name to use - */ - public static function get_class($code) { - if (!$code) { - return StatusUnknown::class; - } - - $class = sprintf('\WpOrg\Requests\Exception\Http\Status%d', $code); - if (class_exists($class)) { - return $class; - } - - return StatusUnknown::class; - } -} diff --git a/bundle/rmccue/requests/src/Exception/Http/Status304.php b/bundle/rmccue/requests/src/Exception/Http/Status304.php deleted file mode 100644 index d510ae7d4..000000000 --- a/bundle/rmccue/requests/src/Exception/Http/Status304.php +++ /dev/null @@ -1,31 +0,0 @@ -code = (int) $data->status_code; - } - - parent::__construct($reason, $data); - } -} diff --git a/bundle/rmccue/requests/src/Exception/InvalidArgument.php b/bundle/rmccue/requests/src/Exception/InvalidArgument.php deleted file mode 100644 index 0ab7332f4..000000000 --- a/bundle/rmccue/requests/src/Exception/InvalidArgument.php +++ /dev/null @@ -1,41 +0,0 @@ -type = $type; - } - - if ($code !== null) { - $this->code = (int) $code; - } - - if ($message !== null) { - $this->reason = $message; - } - - $message = sprintf('%d %s', $this->code, $this->reason); - parent::__construct($message, $this->type, $data, $this->code); - } - - /** - * Get the error message. - * - * @return string - */ - public function getReason() { - return $this->reason; - } - -} diff --git a/bundle/rmccue/requests/src/HookManager.php b/bundle/rmccue/requests/src/HookManager.php deleted file mode 100644 index f2920170b..000000000 --- a/bundle/rmccue/requests/src/HookManager.php +++ /dev/null @@ -1,33 +0,0 @@ -0 is executed later - */ - public function register($hook, $callback, $priority = 0); - - /** - * Dispatch a message - * - * @param string $hook Hook name - * @param array $parameters Parameters to pass to callbacks - * @return boolean Successfulness - */ - public function dispatch($hook, $parameters = []); -} diff --git a/bundle/rmccue/requests/src/Hooks.php b/bundle/rmccue/requests/src/Hooks.php deleted file mode 100644 index 74fba0b3e..000000000 --- a/bundle/rmccue/requests/src/Hooks.php +++ /dev/null @@ -1,99 +0,0 @@ -0 is executed later - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $callback argument is not callable. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $priority argument is not an integer. - */ - public function register($hook, $callback, $priority = 0) { - if (is_string($hook) === false) { - throw InvalidArgument::create(1, '$hook', 'string', gettype($hook)); - } - - if (is_callable($callback) === false) { - throw InvalidArgument::create(2, '$callback', 'callable', gettype($callback)); - } - - if (InputValidator::is_numeric_array_key($priority) === false) { - throw InvalidArgument::create(3, '$priority', 'integer', gettype($priority)); - } - - if (!isset($this->hooks[$hook])) { - $this->hooks[$hook] = [ - $priority => [], - ]; - } elseif (!isset($this->hooks[$hook][$priority])) { - $this->hooks[$hook][$priority] = []; - } - - $this->hooks[$hook][$priority][] = $callback; - } - - /** - * Dispatch a message - * - * @param string $hook Hook name - * @param array $parameters Parameters to pass to callbacks - * @return boolean Successfulness - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $parameters argument is not an array. - */ - public function dispatch($hook, $parameters = []) { - if (is_string($hook) === false) { - throw InvalidArgument::create(1, '$hook', 'string', gettype($hook)); - } - - // Check strictly against array, as Array* objects don't work in combination with `call_user_func_array()`. - if (is_array($parameters) === false) { - throw InvalidArgument::create(2, '$parameters', 'array', gettype($parameters)); - } - - if (empty($this->hooks[$hook])) { - return false; - } - - if (!empty($parameters)) { - // Strip potential keys from the array to prevent them being interpreted as parameter names in PHP 8.0. - $parameters = array_values($parameters); - } - - ksort($this->hooks[$hook]); - - foreach ($this->hooks[$hook] as $priority => $hooked) { - foreach ($hooked as $callback) { - $callback(...$parameters); - } - } - - return true; - } -} diff --git a/bundle/rmccue/requests/src/IdnaEncoder.php b/bundle/rmccue/requests/src/IdnaEncoder.php deleted file mode 100644 index 9f235527b..000000000 --- a/bundle/rmccue/requests/src/IdnaEncoder.php +++ /dev/null @@ -1,412 +0,0 @@ - 0) { - if ($position + $length > $strlen) { - throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); - } - - for ($position++; $remaining > 0; $position++) { - $value = ord($input[$position]); - - // If it is invalid, count the sequence as invalid and reprocess the current byte: - if (($value & 0xC0) !== 0x80) { - throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); - } - - --$remaining; - $character |= ($value & 0x3F) << ($remaining * 6); - } - - $position--; - } - - if (// Non-shortest form sequences are invalid - ($length > 1 && $character <= 0x7F) - || ($length > 2 && $character <= 0x7FF) - || ($length > 3 && $character <= 0xFFFF) - // Outside of range of ucschar codepoints - // Noncharacters - || ($character & 0xFFFE) === 0xFFFE - || ($character >= 0xFDD0 && $character <= 0xFDEF) - || ( - // Everything else not in ucschar - ($character > 0xD7FF && $character < 0xF900) - || $character < 0x20 - || ($character > 0x7E && $character < 0xA0) - || $character > 0xEFFFD - ) - ) { - throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); - } - - $codepoints[] = $character; - } - - return $codepoints; - } - - /** - * RFC3492-compliant encoder - * - * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code - * - * @param string $input UTF-8 encoded string to encode - * @return string Punycode-encoded string - * - * @throws \WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) - */ - public static function punycode_encode($input) { - $output = ''; - // let n = initial_n - $n = self::BOOTSTRAP_INITIAL_N; - // let delta = 0 - $delta = 0; - // let bias = initial_bias - $bias = self::BOOTSTRAP_INITIAL_BIAS; - // let h = b = the number of basic code points in the input - $h = 0; - $b = 0; // see loop - // copy them to the output in order - $codepoints = self::utf8_to_codepoints($input); - $extended = []; - - foreach ($codepoints as $char) { - if ($char < 128) { - // Character is valid ASCII - // TODO: this should also check if it's valid for a URL - $output .= chr($char); - $h++; - - // Check if the character is non-ASCII, but below initial n - // This never occurs for Punycode, so ignore in coverage - // @codeCoverageIgnoreStart - } elseif ($char < $n) { - throw new Exception('Invalid character', 'idna.character_outside_domain', $char); - // @codeCoverageIgnoreEnd - } else { - $extended[$char] = true; - } - } - - $extended = array_keys($extended); - sort($extended); - $b = $h; - // [copy them] followed by a delimiter if b > 0 - if (strlen($output) > 0) { - $output .= '-'; - } - - // {if the input contains a non-basic code point < n then fail} - // while h < length(input) do begin - $codepointcount = count($codepoints); - while ($h < $codepointcount) { - // let m = the minimum code point >= n in the input - $m = array_shift($extended); - //printf('next code point to insert is %s' . PHP_EOL, dechex($m)); - // let delta = delta + (m - n) * (h + 1), fail on overflow - $delta += ($m - $n) * ($h + 1); - // let n = m - $n = $m; - // for each code point c in the input (in order) do begin - for ($num = 0; $num < $codepointcount; $num++) { - $c = $codepoints[$num]; - // if c < n then increment delta, fail on overflow - if ($c < $n) { - $delta++; - } elseif ($c === $n) { // if c == n then begin - // let q = delta - $q = $delta; - // for k = base to infinity in steps of base do begin - for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) { - // let t = tmin if k <= bias {+ tmin}, or - // tmax if k >= bias + tmax, or k - bias otherwise - if ($k <= ($bias + self::BOOTSTRAP_TMIN)) { - $t = self::BOOTSTRAP_TMIN; - } elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) { - $t = self::BOOTSTRAP_TMAX; - } else { - $t = $k - $bias; - } - - // if q < t then break - if ($q < $t) { - break; - } - - // output the code point for digit t + ((q - t) mod (base - t)) - $digit = (int) ($t + (($q - $t) % (self::BOOTSTRAP_BASE - $t))); - $output .= self::digit_to_char($digit); - // let q = (q - t) div (base - t) - $q = (int) floor(($q - $t) / (self::BOOTSTRAP_BASE - $t)); - } // end - // output the code point for digit q - $output .= self::digit_to_char($q); - // let bias = adapt(delta, h + 1, test h equals b?) - $bias = self::adapt($delta, $h + 1, $h === $b); - // let delta = 0 - $delta = 0; - // increment h - $h++; - } // end - } // end - // increment delta and n - $delta++; - $n++; - } // end - - return $output; - } - - /** - * Convert a digit to its respective character - * - * @link https://tools.ietf.org/html/rfc3492#section-5 - * - * @param int $digit Digit in the range 0-35 - * @return string Single character corresponding to digit - * - * @throws \WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`) - */ - protected static function digit_to_char($digit) { - // @codeCoverageIgnoreStart - // As far as I know, this never happens, but still good to be sure. - if ($digit < 0 || $digit > 35) { - throw new Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); - } - - // @codeCoverageIgnoreEnd - $digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; - return substr($digits, $digit, 1); - } - - /** - * Adapt the bias - * - * @link https://tools.ietf.org/html/rfc3492#section-6.1 - * @param int $delta - * @param int $numpoints - * @param bool $firsttime - * @return int|float New bias - * - * function adapt(delta,numpoints,firsttime): - */ - protected static function adapt($delta, $numpoints, $firsttime) { - // if firsttime then let delta = delta div damp - if ($firsttime) { - $delta = floor($delta / self::BOOTSTRAP_DAMP); - } else { - // else let delta = delta div 2 - $delta = floor($delta / 2); - } - - // let delta = delta + (delta div numpoints) - $delta += floor($delta / $numpoints); - // let k = 0 - $k = 0; - // while delta > ((base - tmin) * tmax) div 2 do begin - $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); - while ($delta > $max) { - // let delta = delta div (base - tmin) - $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); - // let k = k + base - $k += self::BOOTSTRAP_BASE; - } // end - // return k + (((base - tmin + 1) * delta) div (delta + skew)) - return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); - } -} diff --git a/bundle/rmccue/requests/src/Ipv6.php b/bundle/rmccue/requests/src/Ipv6.php deleted file mode 100644 index bcdd63649..000000000 --- a/bundle/rmccue/requests/src/Ipv6.php +++ /dev/null @@ -1,203 +0,0 @@ - FF01:0:0:0:0:0:0:101 - * ::1 -> 0:0:0:0:0:0:0:1 - * - * @author Alexander Merz - * @author elfrink at introweb dot nl - * @author Josh Peck - * @copyright 2003-2005 The PHP Group - * @license https://opensource.org/licenses/bsd-license.php - * - * @param string|Stringable $ip An IPv6 address - * @return string The uncompressed IPv6 address - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object. - */ - public static function uncompress($ip) { - if (InputValidator::is_string_or_stringable($ip) === false) { - throw InvalidArgument::create(1, '$ip', 'string|Stringable', gettype($ip)); - } - - $ip = (string) $ip; - - if (substr_count($ip, '::') !== 1) { - return $ip; - } - - list($ip1, $ip2) = explode('::', $ip); - $c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':'); - $c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':'); - - if (strpos($ip2, '.') !== false) { - $c2++; - } - - if ($c1 === -1 && $c2 === -1) { - // :: - $ip = '0:0:0:0:0:0:0:0'; - } elseif ($c1 === -1) { - // ::xxx - $fill = str_repeat('0:', 7 - $c2); - $ip = str_replace('::', $fill, $ip); - } elseif ($c2 === -1) { - // xxx:: - $fill = str_repeat(':0', 7 - $c1); - $ip = str_replace('::', $fill, $ip); - } else { - // xxx::xxx - $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); - $ip = str_replace('::', $fill, $ip); - } - - return $ip; - } - - /** - * Compresses an IPv6 address - * - * RFC 4291 allows you to compress consecutive zero pieces in an address to - * '::'. This method expects a valid IPv6 address and compresses consecutive - * zero pieces to '::'. - * - * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 - * 0:0:0:0:0:0:0:1 -> ::1 - * - * @see \WpOrg\Requests\Ipv6::uncompress() - * - * @param string $ip An IPv6 address - * @return string The compressed IPv6 address - */ - public static function compress($ip) { - // Prepare the IP to be compressed. - // Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method. - $ip = self::uncompress($ip); - $ip_parts = self::split_v6_v4($ip); - - // Replace all leading zeros - $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); - - // Find bunches of zeros - if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { - $max = 0; - $pos = null; - foreach ($matches[0] as $match) { - if (strlen($match[0]) > $max) { - $max = strlen($match[0]); - $pos = $match[1]; - } - } - - $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); - } - - if ($ip_parts[1] !== '') { - return implode(':', $ip_parts); - } else { - return $ip_parts[0]; - } - } - - /** - * Splits an IPv6 address into the IPv6 and IPv4 representation parts - * - * RFC 4291 allows you to represent the last two parts of an IPv6 address - * using the standard IPv4 representation - * - * Example: 0:0:0:0:0:0:13.1.68.3 - * 0:0:0:0:0:FFFF:129.144.52.38 - * - * @param string $ip An IPv6 address - * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part - */ - private static function split_v6_v4($ip) { - if (strpos($ip, '.') !== false) { - $pos = strrpos($ip, ':'); - $ipv6_part = substr($ip, 0, $pos); - $ipv4_part = substr($ip, $pos + 1); - return [$ipv6_part, $ipv4_part]; - } else { - return [$ip, '']; - } - } - - /** - * Checks an IPv6 address - * - * Checks if the given IP is a valid IPv6 address - * - * @param string $ip An IPv6 address - * @return bool true if $ip is a valid IPv6 address - */ - public static function check_ipv6($ip) { - // Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method. - $ip = self::uncompress($ip); - list($ipv6, $ipv4) = self::split_v6_v4($ip); - $ipv6 = explode(':', $ipv6); - $ipv4 = explode('.', $ipv4); - if ((count($ipv6) === 8 && count($ipv4) === 1) || (count($ipv6) === 6 && count($ipv4) === 4)) { - foreach ($ipv6 as $ipv6_part) { - // The section can't be empty - if ($ipv6_part === '') { - return false; - } - - // Nor can it be over four characters - if (strlen($ipv6_part) > 4) { - return false; - } - - // Remove leading zeros (this is safe because of the above) - $ipv6_part = ltrim($ipv6_part, '0'); - if ($ipv6_part === '') { - $ipv6_part = '0'; - } - - // Check the value is valid - $value = hexdec($ipv6_part); - if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) { - return false; - } - } - - if (count($ipv4) === 4) { - foreach ($ipv4 as $ipv4_part) { - $value = (int) $ipv4_part; - if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) { - return false; - } - } - } - - return true; - } else { - return false; - } - } -} diff --git a/bundle/rmccue/requests/src/Iri.php b/bundle/rmccue/requests/src/Iri.php deleted file mode 100644 index 19e0606e5..000000000 --- a/bundle/rmccue/requests/src/Iri.php +++ /dev/null @@ -1,1104 +0,0 @@ - array( - 'port' => Port::ACAP, - ), - 'dict' => array( - 'port' => Port::DICT, - ), - 'file' => array( - 'ihost' => 'localhost', - ), - 'http' => array( - 'port' => Port::HTTP, - ), - 'https' => array( - 'port' => Port::HTTPS, - ), - ); - - /** - * Return the entire IRI when you try and read the object as a string - * - * @return string - */ - public function __toString() { - return $this->get_iri(); - } - - /** - * Overload __set() to provide access via properties - * - * @param string $name Property name - * @param mixed $value Property value - */ - public function __set($name, $value) { - if (method_exists($this, 'set_' . $name)) { - call_user_func(array($this, 'set_' . $name), $value); - } - elseif ( - $name === 'iauthority' - || $name === 'iuserinfo' - || $name === 'ihost' - || $name === 'ipath' - || $name === 'iquery' - || $name === 'ifragment' - ) { - call_user_func(array($this, 'set_' . substr($name, 1)), $value); - } - } - - /** - * Overload __get() to provide access via properties - * - * @param string $name Property name - * @return mixed - */ - public function __get($name) { - // isset() returns false for null, we don't want to do that - // Also why we use array_key_exists below instead of isset() - $props = get_object_vars($this); - - if ( - $name === 'iri' || - $name === 'uri' || - $name === 'iauthority' || - $name === 'authority' - ) { - $method = 'get_' . $name; - $return = $this->$method(); - } - elseif (array_key_exists($name, $props)) { - $return = $this->$name; - } - // host -> ihost - elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) { - $name = $prop; - $return = $this->$prop; - } - // ischeme -> scheme - elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { - $name = $prop; - $return = $this->$prop; - } - else { - trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); - $return = null; - } - - if ($return === null && isset($this->scheme, $this->normalization[$this->scheme][$name])) { - return $this->normalization[$this->scheme][$name]; - } - else { - return $return; - } - } - - /** - * Overload __isset() to provide access via properties - * - * @param string $name Property name - * @return bool - */ - public function __isset($name) { - return (method_exists($this, 'get_' . $name) || isset($this->$name)); - } - - /** - * Overload __unset() to provide access via properties - * - * @param string $name Property name - */ - public function __unset($name) { - if (method_exists($this, 'set_' . $name)) { - call_user_func(array($this, 'set_' . $name), ''); - } - } - - /** - * Create a new IRI object, from a specified string - * - * @param string|Stringable|null $iri - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null. - */ - public function __construct($iri = null) { - if ($iri !== null && InputValidator::is_string_or_stringable($iri) === false) { - throw InvalidArgument::create(1, '$iri', 'string|Stringable|null', gettype($iri)); - } - - $this->set_iri($iri); - } - - /** - * Create a new IRI object by resolving a relative IRI - * - * Returns false if $base is not absolute, otherwise an IRI. - * - * @param \WpOrg\Requests\Iri|string $base (Absolute) Base IRI - * @param \WpOrg\Requests\Iri|string $relative Relative IRI - * @return \WpOrg\Requests\Iri|false - */ - public static function absolutize($base, $relative) { - if (!($relative instanceof self)) { - $relative = new self($relative); - } - if (!$relative->is_valid()) { - return false; - } - elseif ($relative->scheme !== null) { - return clone $relative; - } - - if (!($base instanceof self)) { - $base = new self($base); - } - if ($base->scheme === null || !$base->is_valid()) { - return false; - } - - if ($relative->get_iri() !== '') { - if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { - $target = clone $relative; - $target->scheme = $base->scheme; - } - else { - $target = new self; - $target->scheme = $base->scheme; - $target->iuserinfo = $base->iuserinfo; - $target->ihost = $base->ihost; - $target->port = $base->port; - if ($relative->ipath !== '') { - if ($relative->ipath[0] === '/') { - $target->ipath = $relative->ipath; - } - elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { - $target->ipath = '/' . $relative->ipath; - } - elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { - $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; - } - else { - $target->ipath = $relative->ipath; - } - $target->ipath = $target->remove_dot_segments($target->ipath); - $target->iquery = $relative->iquery; - } - else { - $target->ipath = $base->ipath; - if ($relative->iquery !== null) { - $target->iquery = $relative->iquery; - } - elseif ($base->iquery !== null) { - $target->iquery = $base->iquery; - } - } - $target->ifragment = $relative->ifragment; - } - } - else { - $target = clone $base; - $target->ifragment = null; - } - $target->scheme_normalization(); - return $target; - } - - /** - * Parse an IRI into scheme/authority/path/query/fragment segments - * - * @param string $iri - * @return array - */ - protected function parse_iri($iri) { - $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); - $has_match = preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match); - if (!$has_match) { - throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri); - } - - if ($match[1] === '') { - $match['scheme'] = null; - } - if (!isset($match[3]) || $match[3] === '') { - $match['authority'] = null; - } - if (!isset($match[5])) { - $match['path'] = ''; - } - if (!isset($match[6]) || $match[6] === '') { - $match['query'] = null; - } - if (!isset($match[8]) || $match[8] === '') { - $match['fragment'] = null; - } - return $match; - } - - /** - * Remove dot segments from a path - * - * @param string $input - * @return string - */ - protected function remove_dot_segments($input) { - $output = ''; - while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { - // A: If the input buffer begins with a prefix of "../" or "./", - // then remove that prefix from the input buffer; otherwise, - if (strpos($input, '../') === 0) { - $input = substr($input, 3); - } - elseif (strpos($input, './') === 0) { - $input = substr($input, 2); - } - // B: if the input buffer begins with a prefix of "/./" or "/.", - // where "." is a complete path segment, then replace that prefix - // with "/" in the input buffer; otherwise, - elseif (strpos($input, '/./') === 0) { - $input = substr($input, 2); - } - elseif ($input === '/.') { - $input = '/'; - } - // C: if the input buffer begins with a prefix of "/../" or "/..", - // where ".." is a complete path segment, then replace that prefix - // with "/" in the input buffer and remove the last segment and its - // preceding "/" (if any) from the output buffer; otherwise, - elseif (strpos($input, '/../') === 0) { - $input = substr($input, 3); - $output = substr_replace($output, '', (strrpos($output, '/') ?: 0)); - } - elseif ($input === '/..') { - $input = '/'; - $output = substr_replace($output, '', (strrpos($output, '/') ?: 0)); - } - // D: if the input buffer consists only of "." or "..", then remove - // that from the input buffer; otherwise, - elseif ($input === '.' || $input === '..') { - $input = ''; - } - // E: move the first path segment in the input buffer to the end of - // the output buffer, including the initial "/" character (if any) - // and any subsequent characters up to, but not including, the next - // "/" character or the end of the input buffer - elseif (($pos = strpos($input, '/', 1)) !== false) { - $output .= substr($input, 0, $pos); - $input = substr_replace($input, '', 0, $pos); - } - else { - $output .= $input; - $input = ''; - } - } - return $output . $input; - } - - /** - * Replace invalid character with percent encoding - * - * @param string $text Input string - * @param string $extra_chars Valid characters not in iunreserved or - * iprivate (this is ASCII-only) - * @param bool $iprivate Allow iprivate - * @return string - */ - protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = false) { - // Normalize as many pct-encoded sections as possible - $text = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text); - - // Replace invalid percent characters - $text = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text); - - // Add unreserved and % to $extra_chars (the latter is safe because all - // pct-encoded sections are now valid). - $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; - - // Now replace any bytes that aren't allowed with their pct-encoded versions - $position = 0; - $strlen = strlen($text); - while (($position += strspn($text, $extra_chars, $position)) < $strlen) { - $value = ord($text[$position]); - - // Start position - $start = $position; - - // By default we are valid - $valid = true; - - // No one byte sequences are valid due to the while. - // Two byte sequence: - if (($value & 0xE0) === 0xC0) { - $character = ($value & 0x1F) << 6; - $length = 2; - $remaining = 1; - } - // Three byte sequence: - elseif (($value & 0xF0) === 0xE0) { - $character = ($value & 0x0F) << 12; - $length = 3; - $remaining = 2; - } - // Four byte sequence: - elseif (($value & 0xF8) === 0xF0) { - $character = ($value & 0x07) << 18; - $length = 4; - $remaining = 3; - } - // Invalid byte: - else { - $valid = false; - $length = 1; - $remaining = 0; - } - - if ($remaining) { - if ($position + $length <= $strlen) { - for ($position++; $remaining; $position++) { - $value = ord($text[$position]); - - // Check that the byte is valid, then add it to the character: - if (($value & 0xC0) === 0x80) { - $character |= ($value & 0x3F) << (--$remaining * 6); - } - // If it is invalid, count the sequence as invalid and reprocess the current byte: - else { - $valid = false; - $position--; - break; - } - } - } - else { - $position = $strlen - 1; - $valid = false; - } - } - - // Percent encode anything invalid or not in ucschar - if ( - // Invalid sequences - !$valid - // Non-shortest form sequences are invalid - || $length > 1 && $character <= 0x7F - || $length > 2 && $character <= 0x7FF - || $length > 3 && $character <= 0xFFFF - // Outside of range of ucschar codepoints - // Noncharacters - || ($character & 0xFFFE) === 0xFFFE - || $character >= 0xFDD0 && $character <= 0xFDEF - || ( - // Everything else not in ucschar - $character > 0xD7FF && $character < 0xF900 - || $character < 0xA0 - || $character > 0xEFFFD - ) - && ( - // Everything not in iprivate, if it applies - !$iprivate - || $character < 0xE000 - || $character > 0x10FFFD - ) - ) { - // If we were a character, pretend we weren't, but rather an error. - if ($valid) { - $position--; - } - - for ($j = $start; $j <= $position; $j++) { - $text = substr_replace($text, sprintf('%%%02X', ord($text[$j])), $j, 1); - $j += 2; - $position += 2; - $strlen += 2; - } - } - } - - return $text; - } - - /** - * Callback function for preg_replace_callback. - * - * Removes sequences of percent encoded bytes that represent UTF-8 - * encoded characters in iunreserved - * - * @param array $regex_match PCRE match - * @return string Replacement - */ - protected function remove_iunreserved_percent_encoded($regex_match) { - // As we just have valid percent encoded sequences we can just explode - // and ignore the first member of the returned array (an empty string). - $bytes = explode('%', $regex_match[0]); - - // Initialize the new string (this is what will be returned) and that - // there are no bytes remaining in the current sequence (unsurprising - // at the first byte!). - $string = ''; - $remaining = 0; - - // Loop over each and every byte, and set $value to its value - for ($i = 1, $len = count($bytes); $i < $len; $i++) { - $value = hexdec($bytes[$i]); - - // If we're the first byte of sequence: - if (!$remaining) { - // Start position - $start = $i; - - // By default we are valid - $valid = true; - - // One byte sequence: - if ($value <= 0x7F) { - $character = $value; - $length = 1; - } - // Two byte sequence: - elseif (($value & 0xE0) === 0xC0) { - $character = ($value & 0x1F) << 6; - $length = 2; - $remaining = 1; - } - // Three byte sequence: - elseif (($value & 0xF0) === 0xE0) { - $character = ($value & 0x0F) << 12; - $length = 3; - $remaining = 2; - } - // Four byte sequence: - elseif (($value & 0xF8) === 0xF0) { - $character = ($value & 0x07) << 18; - $length = 4; - $remaining = 3; - } - // Invalid byte: - else { - $valid = false; - $remaining = 0; - } - } - // Continuation byte: - else { - // Check that the byte is valid, then add it to the character: - if (($value & 0xC0) === 0x80) { - $remaining--; - $character |= ($value & 0x3F) << ($remaining * 6); - } - // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: - else { - $valid = false; - $remaining = 0; - $i--; - } - } - - // If we've reached the end of the current byte sequence, append it to Unicode::$data - if (!$remaining) { - // Percent encode anything invalid or not in iunreserved - if ( - // Invalid sequences - !$valid - // Non-shortest form sequences are invalid - || $length > 1 && $character <= 0x7F - || $length > 2 && $character <= 0x7FF - || $length > 3 && $character <= 0xFFFF - // Outside of range of iunreserved codepoints - || $character < 0x2D - || $character > 0xEFFFD - // Noncharacters - || ($character & 0xFFFE) === 0xFFFE - || $character >= 0xFDD0 && $character <= 0xFDEF - // Everything else not in iunreserved (this is all BMP) - || $character === 0x2F - || $character > 0x39 && $character < 0x41 - || $character > 0x5A && $character < 0x61 - || $character > 0x7A && $character < 0x7E - || $character > 0x7E && $character < 0xA0 - || $character > 0xD7FF && $character < 0xF900 - ) { - for ($j = $start; $j <= $i; $j++) { - $string .= '%' . strtoupper($bytes[$j]); - } - } - else { - for ($j = $start; $j <= $i; $j++) { - $string .= chr(hexdec($bytes[$j])); - } - } - } - } - - // If we have any bytes left over they are invalid (i.e., we are - // mid-way through a multi-byte sequence) - if ($remaining) { - for ($j = $start; $j < $len; $j++) { - $string .= '%' . strtoupper($bytes[$j]); - } - } - - return $string; - } - - protected function scheme_normalization() { - if (isset($this->scheme, $this->normalization[$this->scheme])) { - if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { - $this->iuserinfo = null; - } - if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { - $this->ihost = null; - } - if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { - $this->port = null; - } - if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { - $this->ipath = ''; - } - if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { - $this->iquery = null; - } - if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { - $this->ifragment = null; - } - } - if (isset($this->ihost) && empty($this->ipath)) { - $this->ipath = '/'; - } - } - - /** - * Check if the object represents a valid IRI. This needs to be done on each - * call as some things change depending on another part of the IRI. - * - * @return bool - */ - public function is_valid() { - $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; - if ($this->ipath !== '' && - ( - $isauthority && $this->ipath[0] !== '/' || - ( - $this->scheme === null && - !$isauthority && - strpos($this->ipath, ':') !== false && - (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) - ) - ) - ) { - return false; - } - - return true; - } - - /** - * Set the entire IRI. Returns true on success, false on failure (if there - * are any invalid characters). - * - * @param string $iri - * @return bool - */ - protected function set_iri($iri) { - static $cache; - if (!$cache) { - $cache = array(); - } - - if ($iri === null) { - return true; - } - - $iri = (string) $iri; - - if (isset($cache[$iri])) { - list($this->scheme, - $this->iuserinfo, - $this->ihost, - $this->port, - $this->ipath, - $this->iquery, - $this->ifragment, - $return) = $cache[$iri]; - return $return; - } - - $parsed = $this->parse_iri($iri); - - $return = $this->set_scheme($parsed['scheme']) - && $this->set_authority($parsed['authority']) - && $this->set_path($parsed['path']) - && $this->set_query($parsed['query']) - && $this->set_fragment($parsed['fragment']); - - $cache[$iri] = array($this->scheme, - $this->iuserinfo, - $this->ihost, - $this->port, - $this->ipath, - $this->iquery, - $this->ifragment, - $return); - return $return; - } - - /** - * Set the scheme. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @param string $scheme - * @return bool - */ - protected function set_scheme($scheme) { - if ($scheme === null) { - $this->scheme = null; - } - elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { - $this->scheme = null; - return false; - } - else { - $this->scheme = strtolower($scheme); - } - return true; - } - - /** - * Set the authority. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @param string $authority - * @return bool - */ - protected function set_authority($authority) { - static $cache; - if (!$cache) { - $cache = array(); - } - - if ($authority === null) { - $this->iuserinfo = null; - $this->ihost = null; - $this->port = null; - return true; - } - if (isset($cache[$authority])) { - list($this->iuserinfo, - $this->ihost, - $this->port, - $return) = $cache[$authority]; - - return $return; - } - - $remaining = $authority; - if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { - $iuserinfo = substr($remaining, 0, $iuserinfo_end); - $remaining = substr($remaining, $iuserinfo_end + 1); - } - else { - $iuserinfo = null; - } - - if (($port_start = strpos($remaining, ':', (strpos($remaining, ']') ?: 0))) !== false) { - $port = substr($remaining, $port_start + 1); - if ($port === false || $port === '') { - $port = null; - } - $remaining = substr($remaining, 0, $port_start); - } - else { - $port = null; - } - - $return = $this->set_userinfo($iuserinfo) && - $this->set_host($remaining) && - $this->set_port($port); - - $cache[$authority] = array($this->iuserinfo, - $this->ihost, - $this->port, - $return); - - return $return; - } - - /** - * Set the iuserinfo. - * - * @param string $iuserinfo - * @return bool - */ - protected function set_userinfo($iuserinfo) { - if ($iuserinfo === null) { - $this->iuserinfo = null; - } - else { - $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); - $this->scheme_normalization(); - } - - return true; - } - - /** - * Set the ihost. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @param string $ihost - * @return bool - */ - protected function set_host($ihost) { - if ($ihost === null) { - $this->ihost = null; - return true; - } - if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { - if (Ipv6::check_ipv6(substr($ihost, 1, -1))) { - $this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']'; - } - else { - $this->ihost = null; - return false; - } - } - else { - $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); - - // Lowercase, but ignore pct-encoded sections (as they should - // remain uppercase). This must be done after the previous step - // as that can add unescaped characters. - $position = 0; - $strlen = strlen($ihost); - while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { - if ($ihost[$position] === '%') { - $position += 3; - } - else { - $ihost[$position] = strtolower($ihost[$position]); - $position++; - } - } - - $this->ihost = $ihost; - } - - $this->scheme_normalization(); - - return true; - } - - /** - * Set the port. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @param string $port - * @return bool - */ - protected function set_port($port) { - if ($port === null) { - $this->port = null; - return true; - } - - if (strspn($port, '0123456789') === strlen($port)) { - $this->port = (int) $port; - $this->scheme_normalization(); - return true; - } - - $this->port = null; - return false; - } - - /** - * Set the ipath. - * - * @param string $ipath - * @return bool - */ - protected function set_path($ipath) { - static $cache; - if (!$cache) { - $cache = array(); - } - - $ipath = (string) $ipath; - - if (isset($cache[$ipath])) { - $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; - } - else { - $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); - $removed = $this->remove_dot_segments($valid); - - $cache[$ipath] = array($valid, $removed); - $this->ipath = ($this->scheme !== null) ? $removed : $valid; - } - $this->scheme_normalization(); - return true; - } - - /** - * Set the iquery. - * - * @param string $iquery - * @return bool - */ - protected function set_query($iquery) { - if ($iquery === null) { - $this->iquery = null; - } - else { - $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); - $this->scheme_normalization(); - } - return true; - } - - /** - * Set the ifragment. - * - * @param string $ifragment - * @return bool - */ - protected function set_fragment($ifragment) { - if ($ifragment === null) { - $this->ifragment = null; - } - else { - $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); - $this->scheme_normalization(); - } - return true; - } - - /** - * Convert an IRI to a URI (or parts thereof) - * - * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()}) - * @return string|false URI if IRI is valid, false otherwise. - */ - protected function to_uri($iri) { - if (!is_string($iri)) { - return false; - } - - static $non_ascii; - if (!$non_ascii) { - $non_ascii = implode('', range("\x80", "\xFF")); - } - - $position = 0; - $strlen = strlen($iri); - while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) { - $iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1); - $position += 3; - $strlen += 2; - } - - return $iri; - } - - /** - * Get the complete IRI - * - * @return string|false - */ - protected function get_iri() { - if (!$this->is_valid()) { - return false; - } - - $iri = ''; - if ($this->scheme !== null) { - $iri .= $this->scheme . ':'; - } - if (($iauthority = $this->get_iauthority()) !== null) { - $iri .= '//' . $iauthority; - } - $iri .= $this->ipath; - if ($this->iquery !== null) { - $iri .= '?' . $this->iquery; - } - if ($this->ifragment !== null) { - $iri .= '#' . $this->ifragment; - } - - return $iri; - } - - /** - * Get the complete URI - * - * @return string - */ - protected function get_uri() { - return $this->to_uri($this->get_iri()); - } - - /** - * Get the complete iauthority - * - * @return string|null - */ - protected function get_iauthority() { - if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) { - return null; - } - - $iauthority = ''; - if ($this->iuserinfo !== null) { - $iauthority .= $this->iuserinfo . '@'; - } - if ($this->ihost !== null) { - $iauthority .= $this->ihost; - } - if ($this->port !== null) { - $iauthority .= ':' . $this->port; - } - return $iauthority; - } - - /** - * Get the complete authority - * - * @return string - */ - protected function get_authority() { - $iauthority = $this->get_iauthority(); - if (is_string($iauthority)) { - return $this->to_uri($iauthority); - } - else { - return $iauthority; - } - } -} diff --git a/bundle/rmccue/requests/src/Port.php b/bundle/rmccue/requests/src/Port.php deleted file mode 100644 index 554540938..000000000 --- a/bundle/rmccue/requests/src/Port.php +++ /dev/null @@ -1,75 +0,0 @@ -proxy = $args; - } elseif (is_array($args)) { - if (count($args) === 1) { - list($this->proxy) = $args; - } elseif (count($args) === 3) { - list($this->proxy, $this->user, $this->pass) = $args; - $this->use_authentication = true; - } else { - throw ArgumentCount::create( - 'an array with exactly one element or exactly three elements', - count($args), - 'proxyhttpbadargs' - ); - } - } elseif ($args !== null) { - throw InvalidArgument::create(1, '$args', 'array|string|null', gettype($args)); - } - } - - /** - * Register the necessary callbacks - * - * @since 1.6 - * @see \WpOrg\Requests\Proxy\Http::curl_before_send() - * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_socket() - * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_host_path() - * @see \WpOrg\Requests\Proxy\Http::fsockopen_header() - * @param \WpOrg\Requests\Hooks $hooks Hook system - */ - public function register(Hooks $hooks) { - $hooks->register('curl.before_send', [$this, 'curl_before_send']); - - $hooks->register('fsockopen.remote_socket', [$this, 'fsockopen_remote_socket']); - $hooks->register('fsockopen.remote_host_path', [$this, 'fsockopen_remote_host_path']); - if ($this->use_authentication) { - $hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); - } - } - - /** - * Set cURL parameters before the data is sent - * - * @since 1.6 - * @param resource|\CurlHandle $handle cURL handle - */ - public function curl_before_send(&$handle) { - curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); - curl_setopt($handle, CURLOPT_PROXY, $this->proxy); - - if ($this->use_authentication) { - curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); - curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string()); - } - } - - /** - * Alter remote socket information before opening socket connection - * - * @since 1.6 - * @param string $remote_socket Socket connection string - */ - public function fsockopen_remote_socket(&$remote_socket) { - $remote_socket = $this->proxy; - } - - /** - * Alter remote path before getting stream data - * - * @since 1.6 - * @param string $path Path to send in HTTP request string ("GET ...") - * @param string $url Full URL we're requesting - */ - public function fsockopen_remote_host_path(&$path, $url) { - $path = $url; - } - - /** - * Add extra headers to the request before sending - * - * @since 1.6 - * @param string $out HTTP header string - */ - public function fsockopen_header(&$out) { - $out .= sprintf("Proxy-Authorization: Basic %s\r\n", base64_encode($this->get_auth_string())); - } - - /** - * Get the authentication string (user:pass) - * - * @since 1.6 - * @return string - */ - public function get_auth_string() { - return $this->user . ':' . $this->pass; - } -} diff --git a/bundle/rmccue/requests/src/Requests.php b/bundle/rmccue/requests/src/Requests.php deleted file mode 100644 index 3824670f2..000000000 --- a/bundle/rmccue/requests/src/Requests.php +++ /dev/null @@ -1,1099 +0,0 @@ - 10, - 'connect_timeout' => 10, - 'useragent' => 'php-requests/' . self::VERSION, - 'protocol_version' => 1.1, - 'redirected' => 0, - 'redirects' => 10, - 'follow_redirects' => true, - 'blocking' => true, - 'type' => self::GET, - 'filename' => false, - 'auth' => false, - 'proxy' => false, - 'cookies' => false, - 'max_bytes' => false, - 'idn' => true, - 'hooks' => null, - 'transport' => null, - 'verify' => null, - 'verifyname' => true, - ]; - - /** - * Default supported Transport classes. - * - * @since 2.0.0 - * - * @var array - */ - const DEFAULT_TRANSPORTS = [ - Curl::class => Curl::class, - Fsockopen::class => Fsockopen::class, - ]; - - /** - * Selected transport name - * - * Use {@see \WpOrg\Requests\Requests::get_transport()} instead - * - * @var array - */ - public static $transport = []; - - /** - * Registered transport classes - * - * @var array - */ - protected static $transports = []; - - /** - * Default certificate path. - * - * @see \WpOrg\Requests\Requests::get_certificate_path() - * @see \WpOrg\Requests\Requests::set_certificate_path() - * - * @var string - */ - protected static $certificate_path = __DIR__ . '/../certificates/cacert.pem'; - - /** - * All (known) valid deflate, gzip header magic markers. - * - * These markers relate to different compression levels. - * - * @link https://stackoverflow.com/a/43170354/482864 Marker source. - * - * @since 2.0.0 - * - * @var array - */ - private static $magic_compression_headers = [ - "\x1f\x8b" => true, // Gzip marker. - "\x78\x01" => true, // Zlib marker - level 1. - "\x78\x5e" => true, // Zlib marker - level 2 to 5. - "\x78\x9c" => true, // Zlib marker - level 6. - "\x78\xda" => true, // Zlib marker - level 7 to 9. - ]; - - /** - * This is a static class, do not instantiate it - * - * @codeCoverageIgnore - */ - private function __construct() {} - - /** - * Register a transport - * - * @param string $transport Transport class to add, must support the \WpOrg\Requests\Transport interface - */ - public static function add_transport($transport) { - if (empty(self::$transports)) { - self::$transports = self::DEFAULT_TRANSPORTS; - } - - self::$transports[$transport] = $transport; - } - - /** - * Get the fully qualified class name (FQCN) for a working transport. - * - * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. - * @return string FQCN of the transport to use, or an empty string if no transport was - * found which provided the requested capabilities. - */ - protected static function get_transport_class(array $capabilities = []) { - // Caching code, don't bother testing coverage. - // @codeCoverageIgnoreStart - // Array of capabilities as a string to be used as an array key. - ksort($capabilities); - $cap_string = serialize($capabilities); - - // Don't search for a transport if it's already been done for these $capabilities. - if (isset(self::$transport[$cap_string])) { - return self::$transport[$cap_string]; - } - - // Ensure we will not run this same check again later on. - self::$transport[$cap_string] = ''; - // @codeCoverageIgnoreEnd - - if (empty(self::$transports)) { - self::$transports = self::DEFAULT_TRANSPORTS; - } - - // Find us a working transport. - foreach (self::$transports as $class) { - if (!class_exists($class)) { - continue; - } - - $result = $class::test($capabilities); - if ($result === true) { - self::$transport[$cap_string] = $class; - break; - } - } - - return self::$transport[$cap_string]; - } - - /** - * Get a working transport. - * - * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. - * @return \WpOrg\Requests\Transport - * @throws \WpOrg\Requests\Exception If no valid transport is found (`notransport`). - */ - protected static function get_transport(array $capabilities = []) { - $class = self::get_transport_class($capabilities); - - if ($class === '') { - throw new Exception('No working transports found', 'notransport', self::$transports); - } - - return new $class(); - } - - /** - * Checks to see if we have a transport for the capabilities requested. - * - * Supported capabilities can be found in the {@see \WpOrg\Requests\Capability} - * interface as constants. - * - * Example usage: - * `Requests::has_capabilities([Capability::SSL => true])`. - * - * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. - * @return bool Whether the transport has the requested capabilities. - */ - public static function has_capabilities(array $capabilities = []) { - return self::get_transport_class($capabilities) !== ''; - } - - /**#@+ - * @see \WpOrg\Requests\Requests::request() - * @param string $url - * @param array $headers - * @param array $options - * @return \WpOrg\Requests\Response - */ - /** - * Send a GET request - */ - public static function get($url, $headers = [], $options = []) { - return self::request($url, $headers, null, self::GET, $options); - } - - /** - * Send a HEAD request - */ - public static function head($url, $headers = [], $options = []) { - return self::request($url, $headers, null, self::HEAD, $options); - } - - /** - * Send a DELETE request - */ - public static function delete($url, $headers = [], $options = []) { - return self::request($url, $headers, null, self::DELETE, $options); - } - - /** - * Send a TRACE request - */ - public static function trace($url, $headers = [], $options = []) { - return self::request($url, $headers, null, self::TRACE, $options); - } - /**#@-*/ - - /**#@+ - * @see \WpOrg\Requests\Requests::request() - * @param string $url - * @param array $headers - * @param array $data - * @param array $options - * @return \WpOrg\Requests\Response - */ - /** - * Send a POST request - */ - public static function post($url, $headers = [], $data = [], $options = []) { - return self::request($url, $headers, $data, self::POST, $options); - } - /** - * Send a PUT request - */ - public static function put($url, $headers = [], $data = [], $options = []) { - return self::request($url, $headers, $data, self::PUT, $options); - } - - /** - * Send an OPTIONS request - */ - public static function options($url, $headers = [], $data = [], $options = []) { - return self::request($url, $headers, $data, self::OPTIONS, $options); - } - - /** - * Send a PATCH request - * - * Note: Unlike {@see \WpOrg\Requests\Requests::post()} and {@see \WpOrg\Requests\Requests::put()}, - * `$headers` is required, as the specification recommends that should send an ETag - * - * @link https://tools.ietf.org/html/rfc5789 - */ - public static function patch($url, $headers, $data = [], $options = []) { - return self::request($url, $headers, $data, self::PATCH, $options); - } - /**#@-*/ - - /** - * Main interface for HTTP requests - * - * This method initiates a request and sends it via a transport before - * parsing. - * - * The `$options` parameter takes an associative array with the following - * options: - * - * - `timeout`: How long should we wait for a response? - * Note: for cURL, a minimum of 1 second applies, as DNS resolution - * operates at second-resolution only. - * (float, seconds with a millisecond precision, default: 10, example: 0.01) - * - `connect_timeout`: How long should we wait while trying to connect? - * (float, seconds with a millisecond precision, default: 10, example: 0.01) - * - `useragent`: Useragent to send to the server - * (string, default: php-requests/$version) - * - `follow_redirects`: Should we follow 3xx redirects? - * (boolean, default: true) - * - `redirects`: How many times should we redirect before erroring? - * (integer, default: 10) - * - `blocking`: Should we block processing on this request? - * (boolean, default: true) - * - `filename`: File to stream the body to instead. - * (string|boolean, default: false) - * - `auth`: Authentication handler or array of user/password details to use - * for Basic authentication - * (\WpOrg\Requests\Auth|array|boolean, default: false) - * - `proxy`: Proxy details to use for proxy by-passing and authentication - * (\WpOrg\Requests\Proxy|array|string|boolean, default: false) - * - `max_bytes`: Limit for the response body size. - * (integer|boolean, default: false) - * - `idn`: Enable IDN parsing - * (boolean, default: true) - * - `transport`: Custom transport. Either a class name, or a - * transport object. Defaults to the first working transport from - * {@see \WpOrg\Requests\Requests::getTransport()} - * (string|\WpOrg\Requests\Transport, default: {@see \WpOrg\Requests\Requests::getTransport()}) - * - `hooks`: Hooks handler. - * (\WpOrg\Requests\HookManager, default: new WpOrg\Requests\Hooks()) - * - `verify`: Should we verify SSL certificates? Allows passing in a custom - * certificate file as a string. (Using true uses the system-wide root - * certificate store instead, but this may have different behaviour - * across transports.) - * (string|boolean, default: certificates/cacert.pem) - * - `verifyname`: Should we verify the common name in the SSL certificate? - * (boolean, default: true) - * - `data_format`: How should we send the `$data` parameter? - * (string, one of 'query' or 'body', default: 'query' for - * HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH) - * - * @param string|Stringable $url URL to request - * @param array $headers Extra headers to send with the request - * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests - * @param string $type HTTP request type (use Requests constants) - * @param array $options Options for the request (see description for more information) - * @return \WpOrg\Requests\Response - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $type argument is not a string. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) - */ - public static function request($url, $headers = [], $data = [], $type = self::GET, $options = []) { - if (InputValidator::is_string_or_stringable($url) === false) { - throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url)); - } - - if (is_string($type) === false) { - throw InvalidArgument::create(4, '$type', 'string', gettype($type)); - } - - if (is_array($options) === false) { - throw InvalidArgument::create(5, '$options', 'array', gettype($options)); - } - - if (empty($options['type'])) { - $options['type'] = $type; - } - - $options = array_merge(self::get_default_options(), $options); - - self::set_defaults($url, $headers, $data, $type, $options); - - $options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]); - - if (!empty($options['transport'])) { - $transport = $options['transport']; - - if (is_string($options['transport'])) { - $transport = new $transport(); - } - } else { - $need_ssl = (stripos($url, 'https://') === 0); - $capabilities = [Capability::SSL => $need_ssl]; - $transport = self::get_transport($capabilities); - } - - $response = $transport->request($url, $headers, $data, $options); - - $options['hooks']->dispatch('requests.before_parse', [&$response, $url, $headers, $data, $type, $options]); - - return self::parse_response($response, $url, $headers, $data, $options); - } - - /** - * Send multiple HTTP requests simultaneously - * - * The `$requests` parameter takes an associative or indexed array of - * request fields. The key of each request can be used to match up the - * request with the returned data, or with the request passed into your - * `multiple.request.complete` callback. - * - * The request fields value is an associative array with the following keys: - * - * - `url`: Request URL Same as the `$url` parameter to - * {@see \WpOrg\Requests\Requests::request()} - * (string, required) - * - `headers`: Associative array of header fields. Same as the `$headers` - * parameter to {@see \WpOrg\Requests\Requests::request()} - * (array, default: `array()`) - * - `data`: Associative array of data fields or a string. Same as the - * `$data` parameter to {@see \WpOrg\Requests\Requests::request()} - * (array|string, default: `array()`) - * - `type`: HTTP request type (use \WpOrg\Requests\Requests constants). Same as the `$type` - * parameter to {@see \WpOrg\Requests\Requests::request()} - * (string, default: `\WpOrg\Requests\Requests::GET`) - * - `cookies`: Associative array of cookie name to value, or cookie jar. - * (array|\WpOrg\Requests\Cookie\Jar) - * - * If the `$options` parameter is specified, individual requests will - * inherit options from it. This can be used to use a single hooking system, - * or set all the types to `\WpOrg\Requests\Requests::POST`, for example. - * - * In addition, the `$options` parameter takes the following global options: - * - * - `complete`: A callback for when a request is complete. Takes two - * parameters, a \WpOrg\Requests\Response/\WpOrg\Requests\Exception reference, and the - * ID from the request array (Note: this can also be overridden on a - * per-request basis, although that's a little silly) - * (callback) - * - * @param array $requests Requests data (see description for more information) - * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) - * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - */ - public static function request_multiple($requests, $options = []) { - if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { - throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); - } - - if (is_array($options) === false) { - throw InvalidArgument::create(2, '$options', 'array', gettype($options)); - } - - $options = array_merge(self::get_default_options(true), $options); - - if (!empty($options['hooks'])) { - $options['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']); - if (!empty($options['complete'])) { - $options['hooks']->register('multiple.request.complete', $options['complete']); - } - } - - foreach ($requests as $id => &$request) { - if (!isset($request['headers'])) { - $request['headers'] = []; - } - - if (!isset($request['data'])) { - $request['data'] = []; - } - - if (!isset($request['type'])) { - $request['type'] = self::GET; - } - - if (!isset($request['options'])) { - $request['options'] = $options; - $request['options']['type'] = $request['type']; - } else { - if (empty($request['options']['type'])) { - $request['options']['type'] = $request['type']; - } - - $request['options'] = array_merge($options, $request['options']); - } - - self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']); - - // Ensure we only hook in once - if ($request['options']['hooks'] !== $options['hooks']) { - $request['options']['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']); - if (!empty($request['options']['complete'])) { - $request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']); - } - } - } - - unset($request); - - if (!empty($options['transport'])) { - $transport = $options['transport']; - - if (is_string($options['transport'])) { - $transport = new $transport(); - } - } else { - $transport = self::get_transport(); - } - - $responses = $transport->request_multiple($requests, $options); - - foreach ($responses as $id => &$response) { - // If our hook got messed with somehow, ensure we end up with the - // correct response - if (is_string($response)) { - $request = $requests[$id]; - self::parse_multiple($response, $request); - $request['options']['hooks']->dispatch('multiple.request.complete', [&$response, $id]); - } - } - - return $responses; - } - - /** - * Get the default options - * - * @see \WpOrg\Requests\Requests::request() for values returned by this method - * @param boolean $multirequest Is this a multirequest? - * @return array Default option values - */ - protected static function get_default_options($multirequest = false) { - $defaults = static::OPTION_DEFAULTS; - $defaults['verify'] = self::$certificate_path; - - if ($multirequest !== false) { - $defaults['complete'] = null; - } - - return $defaults; - } - - /** - * Get default certificate path. - * - * @return string Default certificate path. - */ - public static function get_certificate_path() { - return self::$certificate_path; - } - - /** - * Set default certificate path. - * - * @param string|Stringable|bool $path Certificate path, pointing to a PEM file. - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or boolean. - */ - public static function set_certificate_path($path) { - if (InputValidator::is_string_or_stringable($path) === false && is_bool($path) === false) { - throw InvalidArgument::create(1, '$path', 'string|Stringable|bool', gettype($path)); - } - - self::$certificate_path = $path; - } - - /** - * Set the default values - * - * The $options parameter is updated with the results. - * - * @param string $url URL to request - * @param array $headers Extra headers to send with the request - * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests - * @param string $type HTTP request type - * @param array $options Options for the request - * @return void - * - * @throws \WpOrg\Requests\Exception When the $url is not an http(s) URL. - */ - protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) { - if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) { - throw new Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url); - } - - if (empty($options['hooks'])) { - $options['hooks'] = new Hooks(); - } - - if (is_array($options['auth'])) { - $options['auth'] = new Basic($options['auth']); - } - - if ($options['auth'] !== false) { - $options['auth']->register($options['hooks']); - } - - if (is_string($options['proxy']) || is_array($options['proxy'])) { - $options['proxy'] = new Http($options['proxy']); - } - - if ($options['proxy'] !== false) { - $options['proxy']->register($options['hooks']); - } - - if (is_array($options['cookies'])) { - $options['cookies'] = new Jar($options['cookies']); - } elseif (empty($options['cookies'])) { - $options['cookies'] = new Jar(); - } - - if ($options['cookies'] !== false) { - $options['cookies']->register($options['hooks']); - } - - if ($options['idn'] !== false) { - $iri = new Iri($url); - $iri->host = IdnaEncoder::encode($iri->ihost); - $url = $iri->uri; - } - - // Massage the type to ensure we support it. - $type = strtoupper($type); - - if (!isset($options['data_format'])) { - if (in_array($type, [self::HEAD, self::GET, self::DELETE], true)) { - $options['data_format'] = 'query'; - } else { - $options['data_format'] = 'body'; - } - } - } - - /** - * HTTP response parser - * - * @param string $headers Full response text including headers and body - * @param string $url Original request URL - * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects - * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects - * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects - * @return \WpOrg\Requests\Response - * - * @throws \WpOrg\Requests\Exception On missing head/body separator (`requests.no_crlf_separator`) - * @throws \WpOrg\Requests\Exception On missing head/body separator (`noversion`) - * @throws \WpOrg\Requests\Exception On missing head/body separator (`toomanyredirects`) - */ - protected static function parse_response($headers, $url, $req_headers, $req_data, $options) { - $return = new Response(); - if (!$options['blocking']) { - return $return; - } - - $return->raw = $headers; - $return->url = (string) $url; - $return->body = ''; - - if (!$options['filename']) { - $pos = strpos($headers, "\r\n\r\n"); - if ($pos === false) { - // Crap! - throw new Exception('Missing header/body separator', 'requests.no_crlf_separator'); - } - - $headers = substr($return->raw, 0, $pos); - // Headers will always be separated from the body by two new lines - `\n\r\n\r`. - $body = substr($return->raw, $pos + 4); - if (!empty($body)) { - $return->body = $body; - } - } - - // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3) - $headers = str_replace("\r\n", "\n", $headers); - // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2) - $headers = preg_replace('/\n[ \t]/', ' ', $headers); - $headers = explode("\n", $headers); - preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches); - if (empty($matches)) { - throw new Exception('Response could not be parsed', 'noversion', $headers); - } - - $return->protocol_version = (float) $matches[1]; - $return->status_code = (int) $matches[2]; - if ($return->status_code >= 200 && $return->status_code < 300) { - $return->success = true; - } - - foreach ($headers as $header) { - list($key, $value) = explode(':', $header, 2); - $value = trim($value); - preg_replace('#(\s+)#i', ' ', $value); - $return->headers[$key] = $value; - } - - if (isset($return->headers['transfer-encoding'])) { - $return->body = self::decode_chunked($return->body); - unset($return->headers['transfer-encoding']); - } - - if (isset($return->headers['content-encoding'])) { - $return->body = self::decompress($return->body); - } - - //fsockopen and cURL compatibility - if (isset($return->headers['connection'])) { - unset($return->headers['connection']); - } - - $options['hooks']->dispatch('requests.before_redirect_check', [&$return, $req_headers, $req_data, $options]); - - if ($return->is_redirect() && $options['follow_redirects'] === true) { - if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) { - if ($return->status_code === 303) { - $options['type'] = self::GET; - } - - $options['redirected']++; - $location = $return->headers['location']; - if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) { - // relative redirect, for compatibility make it absolute - $location = Iri::absolutize($url, $location); - $location = $location->uri; - } - - $hook_args = [ - &$location, - &$req_headers, - &$req_data, - &$options, - $return, - ]; - $options['hooks']->dispatch('requests.before_redirect', $hook_args); - $redirected = self::request($location, $req_headers, $req_data, $options['type'], $options); - $redirected->history[] = $return; - return $redirected; - } elseif ($options['redirected'] >= $options['redirects']) { - throw new Exception('Too many redirects', 'toomanyredirects', $return); - } - } - - $return->redirects = $options['redirected']; - - $options['hooks']->dispatch('requests.after_request', [&$return, $req_headers, $req_data, $options]); - return $return; - } - - /** - * Callback for `transport.internal.parse_response` - * - * Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response - * while still executing a multiple request. - * - * `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object - * - * @param string $response Full response text including headers and body (will be overwritten with Response instance) - * @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()} - * @return void - */ - public static function parse_multiple(&$response, $request) { - try { - $url = $request['url']; - $headers = $request['headers']; - $data = $request['data']; - $options = $request['options']; - $response = self::parse_response($response, $url, $headers, $data, $options); - } catch (Exception $e) { - $response = $e; - } - } - - /** - * Decoded a chunked body as per RFC 2616 - * - * @link https://tools.ietf.org/html/rfc2616#section-3.6.1 - * @param string $data Chunked body - * @return string Decoded body - */ - protected static function decode_chunked($data) { - if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { - return $data; - } - - $decoded = ''; - $encoded = $data; - - while (true) { - $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); - if (!$is_chunked) { - // Looks like it's not chunked after all - return $data; - } - - $length = hexdec(trim($matches[1])); - if ($length === 0) { - // Ignore trailer headers - return $decoded; - } - - $chunk_length = strlen($matches[0]); - $decoded .= substr($encoded, $chunk_length, $length); - $encoded = substr($encoded, $chunk_length + $length + 2); - - if (trim($encoded) === '0' || empty($encoded)) { - return $decoded; - } - } - - // We'll never actually get down here - // @codeCoverageIgnoreStart - } - // @codeCoverageIgnoreEnd - - /** - * Convert a key => value array to a 'key: value' array for headers - * - * @param iterable $dictionary Dictionary of header values - * @return array List of headers - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not iterable. - */ - public static function flatten($dictionary) { - if (InputValidator::is_iterable($dictionary) === false) { - throw InvalidArgument::create(1, '$dictionary', 'iterable', gettype($dictionary)); - } - - $return = []; - foreach ($dictionary as $key => $value) { - $return[] = sprintf('%s: %s', $key, $value); - } - - return $return; - } - - /** - * Decompress an encoded body - * - * Implements gzip, compress and deflate. Guesses which it is by attempting - * to decode. - * - * @param string $data Compressed data in one of the above formats - * @return string Decompressed string - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string. - */ - public static function decompress($data) { - if (is_string($data) === false) { - throw InvalidArgument::create(1, '$data', 'string', gettype($data)); - } - - if (trim($data) === '') { - // Empty body does not need further processing. - return $data; - } - - $marker = substr($data, 0, 2); - if (!isset(self::$magic_compression_headers[$marker])) { - // Not actually compressed. Probably cURL ruining this for us. - return $data; - } - - if (function_exists('gzdecode')) { - $decoded = @gzdecode($data); - if ($decoded !== false) { - return $decoded; - } - } - - if (function_exists('gzinflate')) { - $decoded = @gzinflate($data); - if ($decoded !== false) { - return $decoded; - } - } - - $decoded = self::compatible_gzinflate($data); - if ($decoded !== false) { - return $decoded; - } - - if (function_exists('gzuncompress')) { - $decoded = @gzuncompress($data); - if ($decoded !== false) { - return $decoded; - } - } - - return $data; - } - - /** - * Decompression of deflated string while staying compatible with the majority of servers. - * - * Certain Servers will return deflated data with headers which PHP's gzinflate() - * function cannot handle out of the box. The following function has been created from - * various snippets on the gzinflate() PHP documentation. - * - * Warning: Magic numbers within. Due to the potential different formats that the compressed - * data may be returned in, some "magic offsets" are needed to ensure proper decompression - * takes place. For a simple progmatic way to determine the magic offset in use, see: - * https://core.trac.wordpress.org/ticket/18273 - * - * @since 1.6.0 - * @link https://core.trac.wordpress.org/ticket/18273 - * @link https://www.php.net/gzinflate#70875 - * @link https://www.php.net/gzinflate#77336 - * - * @param string $gz_data String to decompress. - * @return string|bool False on failure. - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string. - */ - public static function compatible_gzinflate($gz_data) { - if (is_string($gz_data) === false) { - throw InvalidArgument::create(1, '$gz_data', 'string', gettype($gz_data)); - } - - if (trim($gz_data) === '') { - return false; - } - - // Compressed data might contain a full zlib header, if so strip it for - // gzinflate() - if (substr($gz_data, 0, 3) === "\x1f\x8b\x08") { - $i = 10; - $flg = ord(substr($gz_data, 3, 1)); - if ($flg > 0) { - if ($flg & 4) { - list($xlen) = unpack('v', substr($gz_data, $i, 2)); - $i += 2 + $xlen; - } - - if ($flg & 8) { - $i = strpos($gz_data, "\0", $i) + 1; - } - - if ($flg & 16) { - $i = strpos($gz_data, "\0", $i) + 1; - } - - if ($flg & 2) { - $i += 2; - } - } - - $decompressed = self::compatible_gzinflate(substr($gz_data, $i)); - if ($decompressed !== false) { - return $decompressed; - } - } - - // If the data is Huffman Encoded, we must first strip the leading 2 - // byte Huffman marker for gzinflate() - // The response is Huffman coded by many compressors such as - // java.util.zip.Deflater, Ruby's Zlib::Deflate, and .NET's - // System.IO.Compression.DeflateStream. - // - // See https://decompres.blogspot.com/ for a quick explanation of this - // data type - $huffman_encoded = false; - - // low nibble of first byte should be 0x08 - list(, $first_nibble) = unpack('h', $gz_data); - - // First 2 bytes should be divisible by 0x1F - list(, $first_two_bytes) = unpack('n', $gz_data); - - if ($first_nibble === 0x08 && ($first_two_bytes % 0x1F) === 0) { - $huffman_encoded = true; - } - - if ($huffman_encoded) { - $decompressed = @gzinflate(substr($gz_data, 2)); - if ($decompressed !== false) { - return $decompressed; - } - } - - if (substr($gz_data, 0, 4) === "\x50\x4b\x03\x04") { - // ZIP file format header - // Offset 6: 2 bytes, General-purpose field - // Offset 26: 2 bytes, filename length - // Offset 28: 2 bytes, optional field length - // Offset 30: Filename field, followed by optional field, followed - // immediately by data - list(, $general_purpose_flag) = unpack('v', substr($gz_data, 6, 2)); - - // If the file has been compressed on the fly, 0x08 bit is set of - // the general purpose field. We can use this to differentiate - // between a compressed document, and a ZIP file - $zip_compressed_on_the_fly = ((0x08 & $general_purpose_flag) === 0x08); - - if (!$zip_compressed_on_the_fly) { - // Don't attempt to decode a compressed zip file - return $gz_data; - } - - // Determine the first byte of data, based on the above ZIP header - // offsets: - $first_file_start = array_sum(unpack('v2', substr($gz_data, 26, 4))); - $decompressed = @gzinflate(substr($gz_data, 30 + $first_file_start)); - if ($decompressed !== false) { - return $decompressed; - } - - return false; - } - - // Finally fall back to straight gzinflate - $decompressed = @gzinflate($gz_data); - if ($decompressed !== false) { - return $decompressed; - } - - // Fallback for all above failing, not expected, but included for - // debugging and preventing regressions and to track stats - $decompressed = @gzinflate(substr($gz_data, 2)); - if ($decompressed !== false) { - return $decompressed; - } - - return false; - } -} diff --git a/bundle/rmccue/requests/src/Response.php b/bundle/rmccue/requests/src/Response.php deleted file mode 100644 index 86a0438ba..000000000 --- a/bundle/rmccue/requests/src/Response.php +++ /dev/null @@ -1,165 +0,0 @@ -headers = new Headers(); - $this->cookies = new Jar(); - } - - /** - * Is the response a redirect? - * - * @return boolean True if redirect (3xx status), false if not. - */ - public function is_redirect() { - $code = $this->status_code; - return in_array($code, [300, 301, 302, 303, 307], true) || $code > 307 && $code < 400; - } - - /** - * Throws an exception if the request was not successful - * - * @param boolean $allow_redirects Set to false to throw on a 3xx as well - * - * @throws \WpOrg\Requests\Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`) - * @throws \WpOrg\Requests\Exception\Http On non-successful status code. Exception class corresponds to "Status" + code (e.g. {@see \WpOrg\Requests\Exception\Http\Status404}) - */ - public function throw_for_status($allow_redirects = true) { - if ($this->is_redirect()) { - if ($allow_redirects !== true) { - throw new Exception('Redirection not allowed', 'response.no_redirects', $this); - } - } elseif (!$this->success) { - $exception = Http::get_class($this->status_code); - throw new $exception(null, $this); - } - } - - /** - * JSON decode the response body. - * - * The method parameters are the same as those for the PHP native `json_decode()` function. - * - * @link https://php.net/json-decode - * - * @param bool|null $associative Optional. When `true`, JSON objects will be returned as associative arrays; - * When `false`, JSON objects will be returned as objects. - * When `null`, JSON objects will be returned as associative arrays - * or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags. - * Defaults to `true` (in contrast to the PHP native default of `null`). - * @param int $depth Optional. Maximum nesting depth of the structure being decoded. - * Defaults to `512`. - * @param int $options Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE, - * JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR. - * Defaults to `0` (no options set). - * - * @return array - * - * @throws \WpOrg\Requests\Exception If `$this->body` is not valid json. - */ - public function decode_body($associative = true, $depth = 512, $options = 0) { - $data = json_decode($this->body, $associative, $depth, $options); - - if (json_last_error() !== JSON_ERROR_NONE) { - $last_error = json_last_error_msg(); - throw new Exception('Unable to parse JSON data: ' . $last_error, 'response.invalid', $this); - } - - return $data; - } -} diff --git a/bundle/rmccue/requests/src/Response/Headers.php b/bundle/rmccue/requests/src/Response/Headers.php deleted file mode 100644 index c931320ee..000000000 --- a/bundle/rmccue/requests/src/Response/Headers.php +++ /dev/null @@ -1,127 +0,0 @@ -data[$offset])) { - return null; - } - - return $this->flatten($this->data[$offset]); - } - - /** - * Set the given item - * - * @param string $offset Item name - * @param string $value Item value - * - * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) - */ - public function offsetSet($offset, $value) { - if ($offset === null) { - throw new Exception('Object is a dictionary, not a list', 'invalidset'); - } - - if (is_string($offset)) { - $offset = strtolower($offset); - } - - if (!isset($this->data[$offset])) { - $this->data[$offset] = []; - } - - $this->data[$offset][] = $value; - } - - /** - * Get all values for a given header - * - * @param string $offset Name of the header to retrieve. - * @return array|null Header values - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key. - */ - public function getValues($offset) { - if (!is_string($offset) && !is_int($offset)) { - throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset)); - } - - if (is_string($offset)) { - $offset = strtolower($offset); - } - - if (!isset($this->data[$offset])) { - return null; - } - - return $this->data[$offset]; - } - - /** - * Flattens a value into a string - * - * Converts an array into a string by imploding values with a comma, as per - * RFC2616's rules for folding headers. - * - * @param string|array $value Value to flatten - * @return string Flattened value - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array. - */ - public function flatten($value) { - if (is_string($value)) { - return $value; - } - - if (is_array($value)) { - return implode(',', $value); - } - - throw InvalidArgument::create(1, '$value', 'string|array', gettype($value)); - } - - /** - * Get an iterator for the data - * - * Converts the internally stored values to a comma-separated string if there is more - * than one value for a key. - * - * @return \ArrayIterator - */ - public function getIterator() { - return new FilteredIterator($this->data, [$this, 'flatten']); - } -} diff --git a/bundle/rmccue/requests/src/Session.php b/bundle/rmccue/requests/src/Session.php deleted file mode 100644 index 000d2526d..000000000 --- a/bundle/rmccue/requests/src/Session.php +++ /dev/null @@ -1,304 +0,0 @@ -useragent = 'X';` - * - * @var array - */ - public $options = []; - - /** - * Create a new session - * - * @param string|Stringable|null $url Base URL for requests - * @param array $headers Default headers for requests - * @param array $data Default data for requests - * @param array $options Default options for requests - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - */ - public function __construct($url = null, $headers = [], $data = [], $options = []) { - if ($url !== null && InputValidator::is_string_or_stringable($url) === false) { - throw InvalidArgument::create(1, '$url', 'string|Stringable|null', gettype($url)); - } - - if (is_array($headers) === false) { - throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); - } - - if (is_array($data) === false) { - throw InvalidArgument::create(3, '$data', 'array', gettype($data)); - } - - if (is_array($options) === false) { - throw InvalidArgument::create(4, '$options', 'array', gettype($options)); - } - - $this->url = $url; - $this->headers = $headers; - $this->data = $data; - $this->options = $options; - - if (empty($this->options['cookies'])) { - $this->options['cookies'] = new Jar(); - } - } - - /** - * Get a property's value - * - * @param string $name Property name. - * @return mixed|null Property value, null if none found - */ - public function __get($name) { - if (isset($this->options[$name])) { - return $this->options[$name]; - } - - return null; - } - - /** - * Set a property's value - * - * @param string $name Property name. - * @param mixed $value Property value - */ - public function __set($name, $value) { - $this->options[$name] = $value; - } - - /** - * Remove a property's value - * - * @param string $name Property name. - */ - public function __isset($name) { - return isset($this->options[$name]); - } - - /** - * Remove a property's value - * - * @param string $name Property name. - */ - public function __unset($name) { - unset($this->options[$name]); - } - - /**#@+ - * @see \WpOrg\Requests\Session::request() - * @param string $url - * @param array $headers - * @param array $options - * @return \WpOrg\Requests\Response - */ - /** - * Send a GET request - */ - public function get($url, $headers = [], $options = []) { - return $this->request($url, $headers, null, Requests::GET, $options); - } - - /** - * Send a HEAD request - */ - public function head($url, $headers = [], $options = []) { - return $this->request($url, $headers, null, Requests::HEAD, $options); - } - - /** - * Send a DELETE request - */ - public function delete($url, $headers = [], $options = []) { - return $this->request($url, $headers, null, Requests::DELETE, $options); - } - /**#@-*/ - - /**#@+ - * @see \WpOrg\Requests\Session::request() - * @param string $url - * @param array $headers - * @param array $data - * @param array $options - * @return \WpOrg\Requests\Response - */ - /** - * Send a POST request - */ - public function post($url, $headers = [], $data = [], $options = []) { - return $this->request($url, $headers, $data, Requests::POST, $options); - } - - /** - * Send a PUT request - */ - public function put($url, $headers = [], $data = [], $options = []) { - return $this->request($url, $headers, $data, Requests::PUT, $options); - } - - /** - * Send a PATCH request - * - * Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()}, - * `$headers` is required, as the specification recommends that should send an ETag - * - * @link https://tools.ietf.org/html/rfc5789 - */ - public function patch($url, $headers, $data = [], $options = []) { - return $this->request($url, $headers, $data, Requests::PATCH, $options); - } - /**#@-*/ - - /** - * Main interface for HTTP requests - * - * This method initiates a request and sends it via a transport before - * parsing. - * - * @see \WpOrg\Requests\Requests::request() - * - * @param string $url URL to request - * @param array $headers Extra headers to send with the request - * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests - * @param string $type HTTP request type (use \WpOrg\Requests\Requests constants) - * @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()}) - * @return \WpOrg\Requests\Response - * - * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) - */ - public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) { - $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); - - return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); - } - - /** - * Send multiple HTTP requests simultaneously - * - * @see \WpOrg\Requests\Requests::request_multiple() - * - * @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()}) - * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) - * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - */ - public function request_multiple($requests, $options = []) { - if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { - throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); - } - - if (is_array($options) === false) { - throw InvalidArgument::create(2, '$options', 'array', gettype($options)); - } - - foreach ($requests as $key => $request) { - $requests[$key] = $this->merge_request($request, false); - } - - $options = array_merge($this->options, $options); - - // Disallow forcing the type, as that's a per request setting - unset($options['type']); - - return Requests::request_multiple($requests, $options); - } - - /** - * Merge a request's data with the default data - * - * @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()}) - * @param boolean $merge_options Should we merge options as well? - * @return array Request data - */ - protected function merge_request($request, $merge_options = true) { - if ($this->url !== null) { - $request['url'] = Iri::absolutize($this->url, $request['url']); - $request['url'] = $request['url']->uri; - } - - if (empty($request['headers'])) { - $request['headers'] = []; - } - - $request['headers'] = array_merge($this->headers, $request['headers']); - - if (empty($request['data'])) { - if (is_array($this->data)) { - $request['data'] = $this->data; - } - } elseif (is_array($request['data']) && is_array($this->data)) { - $request['data'] = array_merge($this->data, $request['data']); - } - - if ($merge_options === true) { - $request['options'] = array_merge($this->options, $request['options']); - - // Disallow forcing the type, as that's a per request setting - unset($request['options']['type']); - } - - return $request; - } -} diff --git a/bundle/rmccue/requests/src/Ssl.php b/bundle/rmccue/requests/src/Ssl.php deleted file mode 100644 index 99da11d8f..000000000 --- a/bundle/rmccue/requests/src/Ssl.php +++ /dev/null @@ -1,182 +0,0 @@ - 0) { - // Whitespace detected. This can never be a dNSName. - return false; - } - - $parts = explode('.', $reference); - if ($parts !== array_filter($parts)) { - // DNSName cannot contain two dots next to each other. - return false; - } - - // Check the first part of the name - $first = array_shift($parts); - - if (strpos($first, '*') !== false) { - // Check that the wildcard is the full part - if ($first !== '*') { - return false; - } - - // Check that we have at least 3 components (including first) - if (count($parts) < 2) { - return false; - } - } - - // Check the remaining parts - foreach ($parts as $part) { - if (strpos($part, '*') !== false) { - return false; - } - } - - // Nothing found, verified! - return true; - } - - /** - * Match a hostname against a dNSName reference - * - * @param string|Stringable $host Requested host - * @param string|Stringable $reference dNSName to match against - * @return boolean Does the domain match? - * @throws \WpOrg\Requests\Exception\InvalidArgument When either of the passed arguments is not a string or a stringable object. - */ - public static function match_domain($host, $reference) { - if (InputValidator::is_string_or_stringable($host) === false) { - throw InvalidArgument::create(1, '$host', 'string|Stringable', gettype($host)); - } - - // Check if the reference is blocklisted first - if (self::verify_reference_name($reference) !== true) { - return false; - } - - // Check for a direct match - if ((string) $host === (string) $reference) { - return true; - } - - // Calculate the valid wildcard match if the host is not an IP address - // Also validates that the host has 3 parts or more, as per Firefox's ruleset, - // as a wildcard reference is only allowed with 3 parts or more, so the - // comparison will never match if host doesn't contain 3 parts or more as well. - if (ip2long($host) === false) { - $parts = explode('.', $host); - $parts[0] = '*'; - $wildcard = implode('.', $parts); - if ($wildcard === (string) $reference) { - return true; - } - } - - return false; - } -} diff --git a/bundle/rmccue/requests/src/Transport.php b/bundle/rmccue/requests/src/Transport.php deleted file mode 100644 index f2e1c6ed7..000000000 --- a/bundle/rmccue/requests/src/Transport.php +++ /dev/null @@ -1,45 +0,0 @@ - $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. - * @return bool Whether the transport can be used. - */ - public static function test($capabilities = []); -} diff --git a/bundle/rmccue/requests/src/Transport/Curl.php b/bundle/rmccue/requests/src/Transport/Curl.php deleted file mode 100644 index 49522f5f9..000000000 --- a/bundle/rmccue/requests/src/Transport/Curl.php +++ /dev/null @@ -1,644 +0,0 @@ -= 8.0. - */ - private $handle; - - /** - * Hook dispatcher instance - * - * @var \WpOrg\Requests\Hooks - */ - private $hooks; - - /** - * Have we finished the headers yet? - * - * @var boolean - */ - private $done_headers = false; - - /** - * If streaming to a file, keep the file pointer - * - * @var resource - */ - private $stream_handle; - - /** - * How many bytes are in the response body? - * - * @var int - */ - private $response_bytes; - - /** - * What's the maximum number of bytes we should keep? - * - * @var int|bool Byte count, or false if no limit. - */ - private $response_byte_limit; - - /** - * Constructor - */ - public function __construct() { - $curl = curl_version(); - $this->version = $curl['version_number']; - $this->handle = curl_init(); - - curl_setopt($this->handle, CURLOPT_HEADER, false); - curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); - if ($this->version >= self::CURL_7_10_5) { - curl_setopt($this->handle, CURLOPT_ENCODING, ''); - } - - if (defined('CURLOPT_PROTOCOLS')) { - // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound - curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - } - - if (defined('CURLOPT_REDIR_PROTOCOLS')) { - // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound - curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - } - } - - /** - * Destructor - */ - public function __destruct() { - if (is_resource($this->handle)) { - // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.curl_closeDeprecated,Generic.PHP.DeprecatedFunctions.Deprecated - curl_close($this->handle); - } - } - - /** - * Perform a request - * - * @param string|Stringable $url URL to request - * @param array $headers Associative array of request headers - * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD - * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation - * @return string Raw HTTP result - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - * @throws \WpOrg\Requests\Exception On a cURL error (`curlerror`) - */ - public function request($url, $headers = [], $data = [], $options = []) { - if (InputValidator::is_string_or_stringable($url) === false) { - throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url)); - } - - if (is_array($headers) === false) { - throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); - } - - if (!is_array($data) && !is_string($data)) { - if ($data === null) { - $data = ''; - } else { - throw InvalidArgument::create(3, '$data', 'array|string', gettype($data)); - } - } - - if (is_array($options) === false) { - throw InvalidArgument::create(4, '$options', 'array', gettype($options)); - } - - $this->hooks = $options['hooks']; - - $this->setup_handle($url, $headers, $data, $options); - - $options['hooks']->dispatch('curl.before_send', [&$this->handle]); - - if ($options['filename'] !== false) { - // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. - $this->stream_handle = @fopen($options['filename'], 'wb'); - if ($this->stream_handle === false) { - $error = error_get_last(); - throw new Exception($error['message'], 'fopen'); - } - } - - $this->response_data = ''; - $this->response_bytes = 0; - $this->response_byte_limit = false; - if ($options['max_bytes'] !== false) { - $this->response_byte_limit = $options['max_bytes']; - } - - if (isset($options['verify'])) { - if ($options['verify'] === false) { - curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); - curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0); - } elseif (is_string($options['verify'])) { - curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']); - } - } - - if (isset($options['verifyname']) && $options['verifyname'] === false) { - curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); - } - - curl_exec($this->handle); - $response = $this->response_data; - - $options['hooks']->dispatch('curl.after_send', []); - - if (curl_errno($this->handle) === CURLE_WRITE_ERROR || curl_errno($this->handle) === CURLE_BAD_CONTENT_ENCODING) { - // Reset encoding and try again - curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); - - $this->response_data = ''; - $this->response_bytes = 0; - curl_exec($this->handle); - $response = $this->response_data; - } - - $this->process_response($response, $options); - - // Need to remove the $this reference from the curl handle. - // Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called. - curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); - curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); - - return $this->headers; - } - - /** - * Send multiple requests simultaneously - * - * @param array $requests Request data - * @param array $options Global options - * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - */ - public function request_multiple($requests, $options) { - // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ - if (empty($requests)) { - return []; - } - - if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { - throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); - } - - if (is_array($options) === false) { - throw InvalidArgument::create(2, '$options', 'array', gettype($options)); - } - - $multihandle = curl_multi_init(); - $subrequests = []; - $subhandles = []; - - $class = get_class($this); - foreach ($requests as $id => $request) { - $subrequests[$id] = new $class(); - $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); - $request['options']['hooks']->dispatch('curl.before_multi_add', [&$subhandles[$id]]); - curl_multi_add_handle($multihandle, $subhandles[$id]); - } - - $completed = 0; - $responses = []; - $subrequestcount = count($subrequests); - - $request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]); - - do { - $active = 0; - - do { - $status = curl_multi_exec($multihandle, $active); - } while ($status === CURLM_CALL_MULTI_PERFORM); - - $to_process = []; - - // Read the information as needed - while ($done = curl_multi_info_read($multihandle)) { - $key = array_search($done['handle'], $subhandles, true); - if (!isset($to_process[$key])) { - $to_process[$key] = $done; - } - } - - // Parse the finished requests before we start getting the new ones - foreach ($to_process as $key => $done) { - $options = $requests[$key]['options']; - if ($done['result'] !== CURLE_OK) { - //get error string for handle. - $reason = curl_error($done['handle']); - $exception = new CurlException( - $reason, - CurlException::EASY, - $done['handle'], - $done['result'] - ); - $responses[$key] = $exception; - $options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]); - } else { - $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); - - $options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]); - } - - curl_multi_remove_handle($multihandle, $done['handle']); - if (is_resource($done['handle'])) { - // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.curl_closeDeprecated,Generic.PHP.DeprecatedFunctions.Deprecated - curl_close($done['handle']); - } - - if (!is_string($responses[$key])) { - $options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]); - } - - $completed++; - } - } while ($active || $completed < $subrequestcount); - - $request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]); - - curl_multi_close($multihandle); - - return $responses; - } - - /** - * Get the cURL handle for use in a multi-request - * - * @param string $url URL to request - * @param array $headers Associative array of request headers - * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD - * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation - * @return resource|\CurlHandle Subrequest's cURL handle - */ - public function &get_subrequest_handle($url, $headers, $data, $options) { - $this->setup_handle($url, $headers, $data, $options); - - if ($options['filename'] !== false) { - $this->stream_handle = fopen($options['filename'], 'wb'); - } - - $this->response_data = ''; - $this->response_bytes = 0; - $this->response_byte_limit = false; - if ($options['max_bytes'] !== false) { - $this->response_byte_limit = $options['max_bytes']; - } - - $this->hooks = $options['hooks']; - - return $this->handle; - } - - /** - * Setup the cURL handle for the given data - * - * @param string $url URL to request - * @param array $headers Associative array of request headers - * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD - * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation - */ - private function setup_handle($url, $headers, $data, $options) { - $options['hooks']->dispatch('curl.before_request', [&$this->handle]); - - // Force closing the connection for old versions of cURL (<7.22). - if (!isset($headers['Connection'])) { - $headers['Connection'] = 'close'; - } - - /** - * Add "Expect" header. - * - * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can - * add as much as a second to the time it takes for cURL to perform a request. To - * prevent this, we need to set an empty "Expect" header. To match the behaviour of - * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use - * HTTP/1.1. - * - * https://curl.se/mail/lib-2017-07/0013.html - */ - if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) { - $headers['Expect'] = $this->get_expect_header($data); - } - - $headers = Requests::flatten($headers); - - if (!empty($data)) { - $data_format = $options['data_format']; - - if ($data_format === 'query') { - $url = self::format_get($url, $data); - $data = ''; - } elseif (!is_string($data)) { - $data = http_build_query($data, '', '&'); - } - } - - switch ($options['type']) { - case Requests::POST: - curl_setopt($this->handle, CURLOPT_POST, true); - curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); - break; - case Requests::HEAD: - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); - curl_setopt($this->handle, CURLOPT_NOBODY, true); - break; - case Requests::TRACE: - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); - break; - case Requests::PATCH: - case Requests::PUT: - case Requests::DELETE: - case Requests::OPTIONS: - default: - curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); - if (!empty($data)) { - curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); - } - } - - // cURL requires a minimum timeout of 1 second when using the system - // DNS resolver, as it uses `alarm()`, which is second resolution only. - // There's no way to detect which DNS resolver is being used from our - // end, so we need to round up regardless of the supplied timeout. - // - // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 - $timeout = max($options['timeout'], 1); - - if (is_int($timeout) || $this->version < self::CURL_7_16_2) { - curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); - } else { - // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound - curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); - } - - if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { - curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout'])); - } else { - // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound - curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); - } - - curl_setopt($this->handle, CURLOPT_URL, $url); - curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); - if (!empty($headers)) { - curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); - } - - if ($options['protocol_version'] === 1.1) { - curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - } else { - curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); - } - - if ($options['blocking'] === true) { - curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']); - curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']); - curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); - } - } - - /** - * Process a response - * - * @param string $response Response data from the body - * @param array $options Request options - * @return string|false HTTP response data including headers. False if non-blocking. - * @throws \WpOrg\Requests\Exception If the request resulted in a cURL error. - */ - public function process_response($response, $options) { - if ($options['blocking'] === false) { - $fake_headers = ''; - $options['hooks']->dispatch('curl.after_request', [&$fake_headers]); - return false; - } - - if ($options['filename'] !== false && $this->stream_handle) { - fclose($this->stream_handle); - $this->headers = trim($this->headers); - } else { - $this->headers .= $response; - } - - if (curl_errno($this->handle)) { - $error = sprintf( - 'cURL error %s: %s', - curl_errno($this->handle), - curl_error($this->handle) - ); - throw new Exception($error, 'curlerror', $this->handle); - } - - $this->info = curl_getinfo($this->handle); - - $options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]); - return $this->headers; - } - - /** - * Collect the headers as they are received - * - * @param resource|\CurlHandle $handle cURL handle - * @param string $headers Header string - * @return integer Length of provided header - */ - public function stream_headers($handle, $headers) { - // Why do we do this? cURL will send both the final response and any - // interim responses, such as a 100 Continue. We don't need that. - // (We may want to keep this somewhere just in case) - if ($this->done_headers) { - $this->headers = ''; - $this->done_headers = false; - } - - $this->headers .= $headers; - - if ($headers === "\r\n") { - $this->done_headers = true; - } - - return strlen($headers); - } - - /** - * Collect data as it's received - * - * @since 1.6.1 - * - * @param resource|\CurlHandle $handle cURL handle - * @param string $data Body data - * @return integer Length of provided data - */ - public function stream_body($handle, $data) { - $this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]); - $data_length = strlen($data); - - // Are we limiting the response size? - if ($this->response_byte_limit) { - if ($this->response_bytes === $this->response_byte_limit) { - // Already at maximum, move on - return $data_length; - } - - if (($this->response_bytes + $data_length) > $this->response_byte_limit) { - // Limit the length - $limited_length = ($this->response_byte_limit - $this->response_bytes); - $data = substr($data, 0, $limited_length); - } - } - - if ($this->stream_handle) { - fwrite($this->stream_handle, $data); - } else { - $this->response_data .= $data; - } - - $this->response_bytes += strlen($data); - return $data_length; - } - - /** - * Format a URL given GET data - * - * @param string $url Original URL. - * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} - * @return string URL with data - */ - private static function format_get($url, $data) { - if (!empty($data)) { - $query = ''; - $url_parts = parse_url($url); - if (empty($url_parts['query'])) { - $url_parts['query'] = ''; - } else { - $query = $url_parts['query']; - } - - $query .= '&' . http_build_query($data, '', '&'); - $query = trim($query, '&'); - - if (empty($url_parts['query'])) { - $url .= '?' . $query; - } else { - $url = str_replace($url_parts['query'], $query, $url); - } - } - - return $url; - } - - /** - * Self-test whether the transport can be used. - * - * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}. - * - * @codeCoverageIgnore - * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. - * @return bool Whether the transport can be used. - */ - public static function test($capabilities = []) { - if (!function_exists('curl_init') || !function_exists('curl_exec')) { - return false; - } - - // If needed, check that our installed curl version supports SSL - if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { - $curl_version = curl_version(); - if (!(CURL_VERSION_SSL & $curl_version['features'])) { - return false; - } - } - - return true; - } - - /** - * Get the correct "Expect" header for the given request data. - * - * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD. - * @return string The "Expect" header. - */ - private function get_expect_header($data) { - if (!is_array($data)) { - return strlen((string) $data) >= 1048576 ? '100-Continue' : ''; - } - - $bytesize = 0; - $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data)); - - foreach ($iterator as $datum) { - $bytesize += strlen((string) $datum); - - if ($bytesize >= 1048576) { - return '100-Continue'; - } - } - - return ''; - } -} diff --git a/bundle/rmccue/requests/src/Transport/Fsockopen.php b/bundle/rmccue/requests/src/Transport/Fsockopen.php deleted file mode 100644 index c8e657a01..000000000 --- a/bundle/rmccue/requests/src/Transport/Fsockopen.php +++ /dev/null @@ -1,520 +0,0 @@ -dispatch('fsockopen.before_request'); - - $url_parts = parse_url($url); - if (empty($url_parts)) { - throw new Exception('Invalid URL.', 'invalidurl', $url); - } - - $host = $url_parts['host']; - $context = stream_context_create(); - $verifyname = false; - $case_insensitive_headers = new CaseInsensitiveDictionary($headers); - - // HTTPS support - if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { - $remote_socket = 'ssl://' . $host; - if (!isset($url_parts['port'])) { - $url_parts['port'] = Port::HTTPS; - } - - $context_options = [ - 'verify_peer' => true, - 'capture_peer_cert' => true, - ]; - $verifyname = true; - - // SNI, if enabled (OpenSSL >=0.9.8j) - // phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound - if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) { - $context_options['SNI_enabled'] = true; - if (isset($options['verifyname']) && $options['verifyname'] === false) { - $context_options['SNI_enabled'] = false; - } - } - - if (isset($options['verify'])) { - if ($options['verify'] === false) { - $context_options['verify_peer'] = false; - $context_options['verify_peer_name'] = false; - $verifyname = false; - } elseif (is_string($options['verify'])) { - $context_options['cafile'] = $options['verify']; - } - } - - if (isset($options['verifyname']) && $options['verifyname'] === false) { - $context_options['verify_peer_name'] = false; - $verifyname = false; - } - - // Handle the PHP 8.4 deprecation (PHP 9.0 removal) of the function signature we use for stream_context_set_option(). - // Ref: https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#stream_context_set_option - if (function_exists('stream_context_set_options')) { - // PHP 8.3+. - // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.stream_context_set_optionsFound - stream_context_set_options($context, ['ssl' => $context_options]); - } else { - // PHP < 8.3. - // phpcs:ignore PHPCompatibility.FunctionUse.OptionalToRequiredFunctionParameters - stream_context_set_option($context, ['ssl' => $context_options]); - } - } else { - $remote_socket = 'tcp://' . $host; - } - - $this->max_bytes = $options['max_bytes']; - - if (!isset($url_parts['port'])) { - $url_parts['port'] = Port::HTTP; - } - - $remote_socket .= ':' . $url_parts['port']; - - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler - set_error_handler([$this, 'connect_error_handler'], E_WARNING | E_NOTICE); - - $options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]); - - $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); - - restore_error_handler(); - - if ($verifyname && !$this->verify_certificate_from_context($host, $context)) { - throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); - } - - if (!$socket) { - if ($errno === 0) { - // Connection issue - throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); - } - - throw new Exception($errstr, 'fsockopenerror', null, $errno); - } - - $data_format = $options['data_format']; - - if ($data_format === 'query') { - $path = self::format_get($url_parts, $data); - $data = ''; - } else { - $path = self::format_get($url_parts, []); - } - - $options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]); - - $request_body = ''; - $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']); - - if ($options['type'] !== Requests::TRACE) { - if (is_array($data)) { - $request_body = http_build_query($data, '', '&'); - } else { - $request_body = $data; - } - - // Always include Content-length on POST requests to prevent - // 411 errors from some servers when the body is empty. - if (!empty($data) || $options['type'] === Requests::POST) { - if (!isset($case_insensitive_headers['Content-Length'])) { - $headers['Content-Length'] = strlen($request_body); - } - - if (!isset($case_insensitive_headers['Content-Type'])) { - $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; - } - } - } - - if (!isset($case_insensitive_headers['Host'])) { - $out .= sprintf('Host: %s', $url_parts['host']); - $scheme_lower = strtolower($url_parts['scheme']); - - if (($scheme_lower === 'http' && $url_parts['port'] !== Port::HTTP) || ($scheme_lower === 'https' && $url_parts['port'] !== Port::HTTPS)) { - $out .= ':' . $url_parts['port']; - } - - $out .= "\r\n"; - } - - if (!isset($case_insensitive_headers['User-Agent'])) { - $out .= sprintf("User-Agent: %s\r\n", $options['useragent']); - } - - $accept_encoding = $this->accept_encoding(); - if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) { - $out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding); - } - - $headers = Requests::flatten($headers); - - if (!empty($headers)) { - $out .= implode("\r\n", $headers) . "\r\n"; - } - - $options['hooks']->dispatch('fsockopen.after_headers', [&$out]); - - if (substr($out, -2) !== "\r\n") { - $out .= "\r\n"; - } - - if (!isset($case_insensitive_headers['Connection'])) { - $out .= "Connection: Close\r\n"; - } - - $out .= "\r\n" . $request_body; - - $options['hooks']->dispatch('fsockopen.before_send', [&$out]); - - fwrite($socket, $out); - $options['hooks']->dispatch('fsockopen.after_send', [$out]); - - if (!$options['blocking']) { - fclose($socket); - $fake_headers = ''; - $options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]); - return ''; - } - - $timeout_sec = (int) floor($options['timeout']); - if ($timeout_sec === $options['timeout']) { - $timeout_msec = 0; - } else { - $timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS; - } - - stream_set_timeout($socket, $timeout_sec, $timeout_msec); - - $response = ''; - $body = ''; - $headers = ''; - $this->info = stream_get_meta_data($socket); - $size = 0; - $doingbody = false; - $download = false; - if ($options['filename']) { - // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. - $download = @fopen($options['filename'], 'wb'); - if ($download === false) { - $error = error_get_last(); - throw new Exception($error['message'], 'fopen'); - } - } - - while (!feof($socket)) { - $this->info = stream_get_meta_data($socket); - if ($this->info['timed_out']) { - throw new Exception('fsocket timed out', 'timeout'); - } - - $block = fread($socket, Requests::BUFFER_SIZE); - if (!$doingbody) { - $response .= $block; - if (strpos($response, "\r\n\r\n")) { - list($headers, $block) = explode("\r\n\r\n", $response, 2); - $doingbody = true; - } - } - - // Are we in body mode now? - if ($doingbody) { - $options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]); - $data_length = strlen($block); - if ($this->max_bytes) { - // Have we already hit a limit? - if ($size === $this->max_bytes) { - continue; - } - - if (($size + $data_length) > $this->max_bytes) { - // Limit the length - $limited_length = ($this->max_bytes - $size); - $block = substr($block, 0, $limited_length); - } - } - - $size += strlen($block); - if ($download) { - fwrite($download, $block); - } else { - $body .= $block; - } - } - } - - $this->headers = $headers; - - if ($download) { - fclose($download); - } else { - $this->headers .= "\r\n\r\n" . $body; - } - - fclose($socket); - - $options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]); - return $this->headers; - } - - /** - * Send multiple requests simultaneously - * - * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()} - * @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation - * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) - * - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. - * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. - */ - public function request_multiple($requests, $options) { - // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ - if (empty($requests)) { - return []; - } - - if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { - throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); - } - - if (is_array($options) === false) { - throw InvalidArgument::create(2, '$options', 'array', gettype($options)); - } - - $responses = []; - $class = get_class($this); - foreach ($requests as $id => $request) { - try { - $handler = new $class(); - $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); - - $request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]); - } catch (Exception $e) { - $responses[$id] = $e; - } - - if (!is_string($responses[$id])) { - $request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]); - } - } - - return $responses; - } - - /** - * Retrieve the encodings we can accept - * - * @return string Accept-Encoding header value - */ - private static function accept_encoding() { - $type = []; - if (function_exists('gzinflate')) { - $type[] = 'deflate;q=1.0'; - } - - if (function_exists('gzuncompress')) { - $type[] = 'compress;q=0.5'; - } - - $type[] = 'gzip;q=0.5'; - - return implode(', ', $type); - } - - /** - * Format a URL given GET data - * - * @param array $url_parts Array of URL parts as received from {@link https://www.php.net/parse_url} - * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} - * @return string URL with data - */ - private static function format_get($url_parts, $data) { - if (!empty($data)) { - if (empty($url_parts['query'])) { - $url_parts['query'] = ''; - } - - $url_parts['query'] .= '&' . http_build_query($data, '', '&'); - $url_parts['query'] = trim($url_parts['query'], '&'); - } - - if (isset($url_parts['path'])) { - if (isset($url_parts['query'])) { - $get = $url_parts['path'] . '?' . $url_parts['query']; - } else { - $get = $url_parts['path']; - } - } else { - $get = '/'; - } - - return $get; - } - - /** - * Error handler for stream_socket_client() - * - * @param int $errno Error number (e.g. E_WARNING) - * @param string $errstr Error message - */ - public function connect_error_handler($errno, $errstr) { - // Double-check we can handle it - if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) { - // Return false to indicate the default error handler should engage - return false; - } - - $this->connect_error .= $errstr . "\n"; - return true; - } - - /** - * Verify the certificate against common name and subject alternative names - * - * Unfortunately, PHP doesn't check the certificate against the alternative - * names, leading things like 'https://www.github.com/' to be invalid. - * Instead - * - * @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 - * - * @param string $host Host name to verify against - * @param resource $context Stream context - * @return bool - * - * @throws \WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) - * @throws \WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) - */ - public function verify_certificate_from_context($host, $context) { - $meta = stream_context_get_options($context); - - // If we don't have SSL options, then we couldn't make the connection at - // all - if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { - throw new Exception(rtrim($this->connect_error), 'ssl.connect_error'); - } - - $cert = openssl_x509_parse($meta['ssl']['peer_certificate']); - - return Ssl::verify_certificate($host, $cert); - } - - /** - * Self-test whether the transport can be used. - * - * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}. - * - * @codeCoverageIgnore - * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. - * @return bool Whether the transport can be used. - */ - public static function test($capabilities = []) { - if (!function_exists('fsockopen')) { - return false; - } - - // If needed, check that streams support SSL - if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { - if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) { - return false; - } - } - - return true; - } -} diff --git a/bundle/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php b/bundle/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php deleted file mode 100644 index d39a9d358..000000000 --- a/bundle/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php +++ /dev/null @@ -1,139 +0,0 @@ - $value) { - $this->offsetSet($offset, $value); - } - } - - /** - * Check if the given item exists - * - * @param string $offset Item key - * @return boolean Does the item exist? - */ - #[ReturnTypeWillChange] - public function offsetExists($offset) { - if (is_string($offset)) { - $offset = strtolower($offset); - } - - if ($offset === null) { - $offset = ''; - } - - return isset($this->data[$offset]); - } - - /** - * Get the value for the item - * - * @param string $offset Item key - * @return string|null Item value (null if the item key doesn't exist) - */ - #[ReturnTypeWillChange] - public function offsetGet($offset) { - if (is_string($offset)) { - $offset = strtolower($offset); - } - - if ($offset === null) { - $offset = ''; - } - - if (!isset($this->data[$offset])) { - return null; - } - - return $this->data[$offset]; - } - - /** - * Set the given item - * - * @param string $offset Item name - * @param string $value Item value - * - * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) - */ - #[ReturnTypeWillChange] - public function offsetSet($offset, $value) { - if ($offset === null) { - throw new Exception('Object is a dictionary, not a list', 'invalidset'); - } - - if (is_string($offset)) { - $offset = strtolower($offset); - } - - $this->data[$offset] = $value; - } - - /** - * Unset the given header - * - * @param string $offset The key for the item to unset. - */ - #[ReturnTypeWillChange] - public function offsetUnset($offset) { - if (is_string($offset)) { - $offset = strtolower($offset); - } - - if ($offset === null) { - $offset = ''; - } - - unset($this->data[$offset]); - } - - /** - * Get an iterator for the data - * - * @return \ArrayIterator - */ - #[ReturnTypeWillChange] - public function getIterator() { - return new ArrayIterator($this->data); - } - - /** - * Get the headers as an array - * - * @return array Header data - */ - public function getAll() { - return $this->data; - } -} diff --git a/bundle/rmccue/requests/src/Utility/FilteredIterator.php b/bundle/rmccue/requests/src/Utility/FilteredIterator.php deleted file mode 100644 index 1039ec1e2..000000000 --- a/bundle/rmccue/requests/src/Utility/FilteredIterator.php +++ /dev/null @@ -1,97 +0,0 @@ -callback = $callback; - } - } - - /** - * Prevent unserialization of the object for security reasons. - * - * @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound - * - * @param array $data Restored array of data originally serialized. - * - * @return void - */ - #[ReturnTypeWillChange] - public function __unserialize($data) {} - // phpcs:enable - - /** - * Perform reinitialization tasks. - * - * Prevents a callback from being injected during unserialization of an object. - * - * @return void - */ - public function __wakeup() { - unset($this->callback); - } - - /** - * Get the current item's value after filtering - * - * @return string - */ - #[ReturnTypeWillChange] - public function current() { - $value = parent::current(); - - if (is_callable($this->callback)) { - $value = call_user_func($this->callback, $value); - } - - return $value; - } - - /** - * Prevent creating a PHP value from a stored representation of the object for security reasons. - * - * @param string $data The serialized string. - * - * @return void - */ - #[ReturnTypeWillChange] - public function unserialize($data) {} -} diff --git a/bundle/rmccue/requests/src/Utility/InputValidator.php b/bundle/rmccue/requests/src/Utility/InputValidator.php deleted file mode 100644 index 7c10d61a4..000000000 --- a/bundle/rmccue/requests/src/Utility/InputValidator.php +++ /dev/null @@ -1,109 +0,0 @@ -=' ) + ); +} else { + // But make sure @less-than-wp tags always exist for those special cases. (Note: @less-than-wp-latest etc won't work and shouldn't be used). + $wp_version_reqs = array_merge( $wp_version_reqs, version_tags( 'less-than-wp', '9999', '>=' ) ); +} + +$skip_tags = array_merge( + $wp_version_reqs, + version_tags( 'require-php', PHP_VERSION, '<' ), + version_tags( 'less-than-php', PHP_VERSION, '>=' ) // Note: this was '>' prior to WP-CLI 1.5.0 but the change is unlikely to cause BC issues as usually compared against major.minor only. +); + +# Skip Github API tests if `GITHUB_TOKEN` not available because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 +if ( ! getenv( 'GITHUB_TOKEN' ) ) { + $skip_tags[] = '@github-api'; +} + +# Skip tests known to be broken. +$skip_tags[] = '@broken'; + +# Require PHP extension, eg 'imagick'. +function extension_tags() { + $extension_tags = array(); + exec( "grep '@require-extension-[A-Za-z_]*' -h -o features/*.feature | uniq", $extension_tags ); + + $skip_tags = array(); + + $substr_start = strlen( '@require-extension-' ); + foreach ( $extension_tags as $tag ) { + $extension = substr( $tag, $substr_start ); + if ( ! extension_loaded( $extension ) ) { + $skip_tags[] = $tag; + } + } + + return $skip_tags; +} + +$skip_tags = array_merge( $skip_tags, extension_tags() ); + +if ( !empty( $skip_tags ) ) { + echo '--tags=~' . implode( '&&~', $skip_tags ); +} + diff --git a/ci/deploy.sh b/ci/deploy.sh new file mode 100755 index 000000000..c1a785543 --- /dev/null +++ b/ci/deploy.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# called by Travis CI + +if [[ "false" != "$TRAVIS_PULL_REQUEST" ]]; then + echo "Not deploying pull requests." + exit +fi + +if [ -z $DEPLOY_BRANCH ]; then + echo "Skipping deployment as DEPLOY_BRANCH is not set" + exit +fi + +if [[ "$TRAVIS_BRANCH" != "$DEPLOY_BRANCH" ]] && [[ ! "$TRAVIS_BRANCH" == "release-"* ]]; then + echo "Skipping deployment as '$TRAVIS_BRANCH' is not a deploy branch." + exit +fi + +# Turn off command traces while dealing with the private key +set +x + +# Get the encrypted private key from the repo settings +echo $WP_CLI_REPO_DEPLOY_KEY | base64 --decode > ~/.ssh/id_rsa +chmod 600 ~/.ssh/id_rsa + +# anyone can read the build log, so it MUST NOT contain any sensitive data +set -x + +# add github's public key +echo "|1|qPmmP7LVZ7Qbpk7AylmkfR0FApQ=|WUy1WS3F4qcr3R5Sc728778goPw= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> ~/.ssh/known_hosts + +git clone git@github.com:wp-cli/builds.git +mv PHAR_BUILD_VERSION builds/phar/NIGHTLY_VERSION +cd builds + +git config user.name "Travis CI" +git config user.email "travis@travis-ci.org" +git config push.default "current" + +if [[ "$TRAVIS_BRANCH" == "release-"* ]]; then + fname="phar/wp-cli-release.phar" +else + fname="phar/wp-cli-nightly.phar" +fi + +mv /tmp/wp-cli-phar/wp $fname +chmod -x $fname + +md5sum $fname | cut -d ' ' -f 1 > $fname.md5 +sha512sum $fname | cut -d ' ' -f 1 > $fname.sha512 + +git add $fname $fname.md5 $fname.sha512 phar/NIGHTLY_VERSION +git commit -m "phar build: $TRAVIS_REPO_SLUG@$TRAVIS_COMMIT" + +git push diff --git a/ci/prepare.sh b/ci/prepare.sh new file mode 100755 index 000000000..650100d8f --- /dev/null +++ b/ci/prepare.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# called by Travis CI + +set -ex + +WP_CLI_BIN_DIR=${WP_CLI_BIN_DIR-/tmp/wp-cli-phar} + +# Disable XDebug to speed up Composer and test suites. +if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then + phpenv config-rm xdebug.ini +else + echo "xdebug.ini does not exist" +fi + +composer install --no-interaction --prefer-source + +CLI_VERSION=$(head -n 1 VERSION) +if [[ $CLI_VERSION == *"-alpha"* ]] +then + GIT_HASH=$(git rev-parse HEAD) + GIT_SHORT_HASH=${GIT_HASH:0:7} + CLI_VERSION="$CLI_VERSION-$GIT_SHORT_HASH" +fi + +# the Behat test suite will pick up the executable found in $WP_CLI_BIN_DIR +if [[ $BUILD == 'git' || $BUILD == 'sniff' ]] +then + echo $CLI_VERSION > VERSION +else + mkdir -p $WP_CLI_BIN_DIR + php -dphar.readonly=0 utils/make-phar.php wp-cli.phar --quiet --version=$CLI_VERSION + mv wp-cli.phar $WP_CLI_BIN_DIR/wp + chmod +x $WP_CLI_BIN_DIR/wp +fi + +echo $CLI_VERSION > PHAR_BUILD_VERSION + +mysql -e 'CREATE DATABASE wp_cli_test;' -uroot +mysql -e 'GRANT ALL PRIVILEGES ON wp_cli_test.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "password1"' -uroot diff --git a/ci/sniff.sh b/ci/sniff.sh new file mode 100755 index 000000000..2c0ec7720 --- /dev/null +++ b/ci/sniff.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -ex + +# Run CodeSniffer +vendor/bin/phpcs diff --git a/ci/test.sh b/ci/test.sh new file mode 100755 index 000000000..a1aba1863 --- /dev/null +++ b/ci/test.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -ex + +# Run the unit tests +phpunit + +BEHAT_TAGS=$(php ci/behat-tags.php) + +# Run the functional tests +vendor/bin/behat --format progress $BEHAT_TAGS --strict diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index e69de29bb..000000000 diff --git a/composer.json b/composer.json index 0d953da49..366c30308 100644 --- a/composer.json +++ b/composer.json @@ -1,95 +1,90 @@ { - "name": "wp-cli/wp-cli", - "description": "WP-CLI framework", - "keywords": [ - "cli", - "wordpress" - ], - "homepage": "https://wp-cli.org", - "license": "MIT", - "require": { - "php": ">=7.2.24 || ^8.0", - "mustache/mustache": "^3.0.0", - "wp-cli/mustangostang-spyc": "^0.6.3", - "wp-cli/php-cli-tools": "~0.12.7" - }, - "require-dev": { - "justinrainbow/json-schema": "^6.3", - "roave/security-advisories": "dev-latest", - "wp-cli/db-command": "^2", - "wp-cli/entity-command": "^2", - "wp-cli/extension-command": "^2", - "wp-cli/package-command": "^2", - "wp-cli/wp-cli-tests": "^5" - }, - "suggest": { - "ext-curl": "For better performance when making HTTP requests", - "ext-readline": "Include for a better --prompt implementation", - "ext-zip": "Needed to support extraction of ZIP archives when doing downloads or updates" - }, - "config": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "johnpbloch/wordpress-core-installer": true, - "phpstan/extension-installer": true - }, - "process-timeout": 7200, - "sort-packages": true, - "lock": false - }, - "extra": { - "branch-alias": { - "dev-main": "2.13.x-dev" - }, - "commands": [ - "cli", - "cli alias", - "cli cache", - "cli check-update", - "cli cmd-dump", - "cli completions", - "cli has-command", - "cli info", - "cli param-dump", - "cli update", - "cli version" - ] - }, - "autoload": { - "psr-0": { - "WP_CLI\\": "php/" - }, - "classmap": [ - "php/class-wp-cli.php", - "php/class-wp-cli-command.php" - ] - }, - "minimum-stability": "dev", - "prefer-stable": true, - "bin": [ - "bin/wp", - "bin/wp.bat" - ], - "scripts": { - "behat": "run-behat-tests", - "behat-rerun": "rerun-behat-tests", - "lint": "run-linter-tests", - "phpcs": "run-phpcs-tests", - "phpcbf": "run-phpcbf-cleanup", - "phpstan": "run-phpstan-tests", - "phpunit": "run-php-unit-tests", - "prepare-tests": "install-package-tests", - "test": [ - "@lint", - "@phpcs", - "@phpstan", - "@phpunit", - "@behat" - ] - }, - "support": { - "issues": "https://github.com/wp-cli/wp-cli/issues", - "source": "https://github.com/wp-cli/wp-cli", - "docs": "https://make.wordpress.org/cli/handbook/" - } + "name": "wp-cli/wp-cli", + "description": "The command line interface for WordPress", + "keywords": [ "cli", "wordpress" ], + "homepage": "https://wp-cli.org", + "license": "MIT", + "support": { + "issues": "https://github.com/wp-cli/wp-cli/issues", + "source": "https://github.com/wp-cli/wp-cli", + "docs": "https://make.wordpress.org/cli/handbook/" + }, + "bin": [ + "bin/wp.bat", "bin/wp" + ], + "config": { + "platform": { + "php": "5.3.29" + }, + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=5.3.29", + "composer/composer": "^1.2.0", + "composer/semver": "~1.0", + "justinrainbow/json-schema": "~5.2.5", + "mustache/mustache": "~2.4", + "ramsey/array_column": "~1.1", + "rmccue/requests": "~1.6", + "symfony/config": "^2.7|^3.0", + "symfony/console": "^2.7|^3.0", + "symfony/debug": "^2.7|^3.0", + "symfony/dependency-injection": "^2.7|^3.0", + "symfony/event-dispatcher": "^2.7|^3.0", + "symfony/filesystem": "^2.7|^3.0", + "symfony/finder": "^2.7|^3.0", + "symfony/process": "^2.1|^3.0", + "symfony/translation": "^2.7|^3.0", + "symfony/yaml": "^2.7|^3.0", + "wp-cli/autoload-splitter": "^0.1.5", + "wp-cli/cache-command": "^1.0", + "wp-cli/checksum-command": "^1.0", + "wp-cli/config-command": "^1.0", + "wp-cli/core-command": "^1.0", + "wp-cli/cron-command": "^1.0", + "wp-cli/db-command": "^1.0", + "wp-cli/entity-command": "^1.0", + "wp-cli/eval-command": "^1.0", + "wp-cli/export-command": "^1.0", + "wp-cli/extension-command": "^1.0", + "wp-cli/import-command": "^1.0", + "wp-cli/language-command": "^1.0", + "wp-cli/media-command": "^1.0", + "wp-cli/mustangostang-spyc": "^0.6.3", + "wp-cli/package-command": "^1.0", + "wp-cli/php-cli-tools": "~0.11.2", + "wp-cli/rewrite-command": "^1.0", + "wp-cli/role-command": "^1.0", + "wp-cli/scaffold-command": "^1.0", + "wp-cli/search-replace-command": "^1.0", + "wp-cli/server-command": "^1.0", + "wp-cli/shell-command": "^1.0", + "wp-cli/super-admin-command": "^1.0", + "wp-cli/widget-command": "^1.0" + }, + "require-dev": { + "behat/behat": "2.5.*", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3", + "phpunit/phpunit": "3.7.*", + "roave/security-advisories": "dev-master", + "wimg/php-compatibility": "^8.0", + "wp-coding-standards/wpcs": "^0.13.1" + }, + "suggest": { + "psy/psysh": "Enhanced `wp shell` functionality" + }, + "autoload": { + "psr-0": { "WP_CLI": "php" }, + "psr-4": { "": "php/commands/src" } + }, + "extra": { + "autoload-splitter": { + "splitter-logic": "WP_CLI\\AutoloadSplitter", + "splitter-location": "php/WP_CLI/AutoloadSplitter.php", + "split-target-prefix-true": "autoload_commands", + "split-target-prefix-false": "autoload_framework" + } + } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..50c62cafe --- /dev/null +++ b/composer.lock @@ -0,0 +1,3764 @@ +{ + "_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#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "0dc5e25ccc983dbe1ac137a5638b8654", + "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/943b2c4fcad1ef178d16a713c2468bf7e579c288", + "reference": "943b2c4fcad1ef178d16a713c2468bf7e579c288", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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" + ], + "time": "2017-11-29T09:37:33+00:00" + }, + { + "name": "composer/composer", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "4f7f9c12753ec43f1e4629e2a71cabe81f2a4eab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/4f7f9c12753ec43f1e4629e2a71cabe81f2a4eab", + "reference": "4f7f9c12753ec43f1e4629e2a71cabe81f2a4eab", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/semver": "^1.0", + "composer/spdx-licenses": "^1.0", + "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0", + "seld/cli-prompt": "^1.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.7 || ^3.0", + "symfony/filesystem": "^2.7 || ^3.0", + "symfony/finder": "^2.7 || ^3.0", + "symfony/process": "^2.7 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "^2.3 || ^3.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" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "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" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "time": "2017-12-18T11:09:18+00:00" + }, + { + "name": "composer/semver", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", + "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.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" + ], + "time": "2016-08-30T16:08:34+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/2603a0d7ddc00a015deb576fa5297ca43dee6b1c", + "reference": "2603a0d7ddc00a015deb576fa5297ca43dee6b1c", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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" + ], + "time": "2017-04-03T19:08:52+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.6", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/d283e11b6e14c6f4664cf080415c4341293e5bbd", + "reference": "d283e11b6e14c6f4664cf080415c4341293e5bbd", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.22" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.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/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2017-10-21T13:15:38+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.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" + ], + "time": "2017-07-11T12:54:05+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" + ], + "time": "2013-02-24T15:01:54+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "ramsey/array_column", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/array_column.git", + "reference": "f8e52eb28e67eb50e613b451dd916abcf783c1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/array_column/zipball/f8e52eb28e67eb50e613b451dd916abcf783c1db", + "reference": "f8e52eb28e67eb50e613b451dd916abcf783c1db", + "shasum": "" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "0.8.*", + "phpunit/phpunit": "~4.5", + "satooshi/php-coveralls": "0.6.*", + "squizlabs/php_codesniffer": "~2.2" + }, + "type": "library", + "autoload": { + "files": [ + "src/array_column.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "homepage": "http://benramsey.com" + } + ], + "description": "Provides functionality for array_column() to projects using PHP earlier than version 5.5.", + "homepage": "https://github.com/ramsey/array_column", + "keywords": [ + "array", + "array_column", + "column" + ], + "time": "2015-03-20T22:07:39+00:00" + }, + { + "name": "rmccue/requests", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/rmccue/Requests.git", + "reference": "87932f52ffad70504d93f04f15690cf16a089546" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rmccue/Requests/zipball/87932f52ffad70504d93f04f15690cf16a089546", + "reference": "87932f52ffad70504d93f04f15690cf16a089546", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "requests/test-server": "dev-master" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/rmccue/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "time": "2016-10-13T00:11:37+00:00" + }, + { + "name": "seld/cli-prompt", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/cli-prompt.git", + "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/a19a7376a4689d4d94cab66ab4f3c816019ba8dd", + "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\CliPrompt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", + "keywords": [ + "cli", + "console", + "hidden", + "input", + "prompt" + ], + "time": "2017-03-18T11:32:45+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.6.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", + "reference": "7a30649c67ee0d19faacfd9fa2cfb6cc032d9b19", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5" + }, + "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": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "time": "2017-11-30T15:34:22+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "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": [ + "phra" + ], + "time": "2015-10-13T18:44:15+00:00" + }, + { + "name": "symfony/config", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "f4f3f1d7090c464434bbbc3e8aa2b41149c59196" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/f4f3f1d7090c464434bbbc3e8aa2b41149c59196", + "reference": "f4f3f1d7090c464434bbbc3e8aa2b41149c59196", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0" + }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2017-11-07T11:56:23+00:00" + }, + { + "name": "symfony/console", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/46270f1ca44f08ebc134ce120fd2c2baf5fd63de", + "reference": "46270f1ca44f08ebc134ce120fd2c2baf5fd63de", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2017-11-29T09:33:18+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/e72a0340dc2e273b3c4398d8eef9157ba51d8b95", + "reference": "e72a0340dc2e273b3c4398d8eef9157ba51d8b95", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "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 Debug Component", + "homepage": "https://symfony.com", + "time": "2017-11-19T19:05:05+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "d3e81e5402c38500770eb5595d78a6d85ea9e412" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/d3e81e5402c38500770eb5595d78a6d85ea9e412", + "reference": "d3e81e5402c38500770eb5595d78a6d85ea9e412", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "require-dev": { + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2017-11-23T11:13:33+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b59aacf238fadda50d612c9de73b74751872a903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b59aacf238fadda50d612c9de73b74751872a903", + "reference": "b59aacf238fadda50d612c9de73b74751872a903", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "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 EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-11-05T15:25:56+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", + "reference": "15ceb6736a9eebd0d99f9e05a62296ab6ce1cf2b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2017-11-19T18:39:05+00:00" + }, + { + "name": "symfony/finder", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", + "reference": "efeceae6a05a9b2fcb3391333f1d4a828ff44ab8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2017-11-05T15:25:56+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "reference": "2ec8b39c38cb16674bbf3fea2b6ce5bf117e1296", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.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": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-10-11T12:05:26+00:00" + }, + { + "name": "symfony/process", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d25449e031f600807949aab7cadbf267712f4eee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d25449e031f600807949aab7cadbf267712f4eee", + "reference": "d25449e031f600807949aab7cadbf267712f4eee", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "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", + "time": "2017-11-05T15:25:56+00:00" + }, + { + "name": "symfony/translation", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "0c63d56516c4c4c323228ca6348eadb7c91b1daf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/0c63d56516c4c4c323228ca6348eadb7c91b1daf", + "reference": "0c63d56516c4c4c323228ca6348eadb7c91b1daf", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8", + "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", + "symfony/yaml": "~2.2|~3.0.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "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 Translation Component", + "homepage": "https://symfony.com", + "time": "2017-11-07T14:08:47+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.8.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "968ef42161e4bc04200119da473077f9e7015128" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/968ef42161e4bc04200119da473077f9e7015128", + "reference": "968ef42161e4bc04200119da473077f9e7015128", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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 Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-11-29T09:33:18+00:00" + }, + { + "name": "wp-cli/autoload-splitter", + "version": "v0.1.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/autoload-splitter.git", + "reference": "fb4302da26390811d2631c62b42b75976d224bb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/autoload-splitter/zipball/fb4302da26390811d2631c62b42b75976d224bb8", + "reference": "fb4302da26390811d2631c62b42b75976d224bb8", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1" + }, + "type": "composer-plugin", + "extra": { + "class": "WP_CLI\\AutoloadSplitter\\ComposerPlugin" + }, + "autoload": { + "psr-4": { + "WP_CLI\\AutoloadSplitter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alain Schlesser", + "email": "alain.schlesser@gmail.com", + "homepage": "https://www.alainschlesser.com" + } + ], + "description": "Composer plugin for splitting a generated autoloader into two distinct parts.", + "homepage": "https://wp-cli.org", + "time": "2017-08-03T08:40:17+00:00" + }, + { + "name": "wp-cli/cache-command", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/cache-command.git", + "reference": "d82cba9effa198f17847dce5771c8fb20c443ffa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/cache-command/zipball/d82cba9effa198f17847dce5771c8fb20c443ffa", + "reference": "d82cba9effa198f17847dce5771c8fb20c443ffa", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "cache", + "cache add", + "cache decr", + "cache delete", + "cache flush", + "cache get", + "cache incr", + "cache replace", + "cache set", + "cache type", + "transient", + "transient delete", + "transient get", + "transient set", + "transient type" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "cache-command.php" + ] + }, + "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", + "time": "2017-12-14T19:21:19+00:00" + }, + { + "name": "wp-cli/checksum-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/checksum-command.git", + "reference": "a2719a5ba84ffd7d47511967433da7211e0da55a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/checksum-command/zipball/a2719a5ba84ffd7d47511967433da7211e0da55a", + "reference": "a2719a5ba84ffd7d47511967433da7211e0da55a", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "checksum core" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "checksum-command.php" + ] + }, + "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", + "time": "2017-12-08T15:12:46+00:00" + }, + { + "name": "wp-cli/config-command", + "version": "v1.1.7", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/config-command.git", + "reference": "e0c378457e6eab81ab0cc322aba86afdad57ec45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/config-command/zipball/e0c378457e6eab81ab0cc322aba86afdad57ec45", + "reference": "e0c378457e6eab81ab0cc322aba86afdad57ec45", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "config", + "config create", + "config get", + "config path" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "config-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manage the wp-config.php file.", + "homepage": "https://github.com/wp-cli/config-command", + "time": "2017-12-08T15:12:01+00:00" + }, + { + "name": "wp-cli/core-command", + "version": "v1.0.8", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/core-command.git", + "reference": "b1c3c1bd10d081c24b606fcc6ed89100d59d4218" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/core-command/zipball/b1c3c1bd10d081c24b606fcc6ed89100d59d4218", + "reference": "b1c3c1bd10d081c24b606fcc6ed89100d59d4218", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "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" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "core-command.php" + ] + }, + "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", + "time": "2017-12-19T22:21:54+00:00" + }, + { + "name": "wp-cli/cron-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/cron-command.git", + "reference": "9da7e36e8f9c14cb171a3c5204cba2865e0ed7ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/cron-command/zipball/9da7e36e8f9c14cb171a3c5204cba2865e0ed7ef", + "reference": "9da7e36e8f9c14cb171a3c5204cba2865e0ed7ef", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "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" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "cron-command.php" + ] + }, + "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", + "time": "2017-12-08T15:09:54+00:00" + }, + { + "name": "wp-cli/db-command", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/db-command.git", + "reference": "162d72acfd6dc1478e2ef249160bffbfa4293371" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/db-command/zipball/162d72acfd6dc1478e2ef249160bffbfa4293371", + "reference": "162d72acfd6dc1478e2ef249160bffbfa4293371", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "db", + "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" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "db-command.php" + ] + }, + "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", + "time": "2017-12-08T15:04:48+00:00" + }, + { + "name": "wp-cli/entity-command", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/entity-command.git", + "reference": "c3a0c2b478aeb385865e6c406b2ecb1fe1900cb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/entity-command/zipball/c3a0c2b478aeb385865e6c406b2ecb1fe1900cb5", + "reference": "c3a0c2b478aeb385865e6c406b2ecb1fe1900cb5", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "phpunit/phpunit": "^4.8", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "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", + "post", + "post create", + "post delete", + "post edit", + "post generate", + "post get", + "post list", + "post meta", + "post meta add", + "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-type", + "post-type get", + "post-type list", + "site", + "site activate", + "site archive", + "site create", + "site deactivate", + "site delete", + "site empty", + "site list", + "site mature", + "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 create", + "user delete", + "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 session", + "user session destroy", + "user session list", + "user set-role", + "user spam", + "user term", + "user term add", + "user term list", + "user term remove", + "user term set", + "user unspam", + "user update" + ] + }, + "autoload": { + "psr-4": { + "": "src/", + "WP_CLI\\": "src/WP_CLI" + }, + "files": [ + "entity-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manage WordPress core entities.", + "homepage": "https://github.com/wp-cli/entity-command", + "time": "2017-12-18T20:05:12+00:00" + }, + { + "name": "wp-cli/eval-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/eval-command.git", + "reference": "9640d40ab28cd86590396f08f8c382e659f57321" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/eval-command/zipball/9640d40ab28cd86590396f08f8c382e659f57321", + "reference": "9640d40ab28cd86590396f08f8c382e659f57321", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "eval", + "eval-file" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "eval-command.php" + ] + }, + "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", + "time": "2017-12-08T14:33:34+00:00" + }, + { + "name": "wp-cli/export-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/export-command.git", + "reference": "b8469df00d0e357bbc8a51ffd006ace007449b15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/export-command/zipball/b8469df00d0e357bbc8a51ffd006ace007449b15", + "reference": "b8469df00d0e357bbc8a51ffd006ace007449b15", + "shasum": "" + }, + "require": { + "nb/oxymel": "~0.1.0", + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "export" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "export-command.php" + ] + }, + "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", + "time": "2017-12-14T19:06:14+00:00" + }, + { + "name": "wp-cli/extension-command", + "version": "v1.1.8", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/extension-command.git", + "reference": "7ab2a957383ee63e5082f4854479738c0f2a4474" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/extension-command/zipball/7ab2a957383ee63e5082f4854479738c0f2a4474", + "reference": "7ab2a957383ee63e5082f4854479738c0f2a4474", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "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" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "extension-command.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Bachhuber", + "email": "daniel@runcommand.io", + "homepage": "https://runcommand.io" + } + ], + "description": "Manages plugins and themes, including installs, activations, and updates.", + "homepage": "https://github.com/wp-cli/extension-command", + "time": "2017-12-08T17:49:39+00:00" + }, + { + "name": "wp-cli/import-command", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/import-command.git", + "reference": "d2c21d590a1bfae6ac4e289a0b57fb1870b5990c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/import-command/zipball/d2c21d590a1bfae6ac4e289a0b57fb1870b5990c", + "reference": "d2c21d590a1bfae6ac4e289a0b57fb1870b5990c", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "bundled": true, + "commands": [ + "import" + ] + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "import-command.php" + ] + }, + "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", + "time": "2017-12-08T15:13:36+00:00" + }, + { + "name": "wp-cli/language-command", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/language-command.git", + "reference": "2a3d1ce5a722a4d70809619a065087aa933f6209" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/language-command/zipball/2a3d1ce5a722a4d70809619a065087aa933f6209", + "reference": "2a3d1ce5a722a4d70809619a065087aa933f6209", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "language", + "language core", + "language core activate", + "language core install", + "language core list", + "language core uninstall", + "language core update" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "language-command.php" + ] + }, + "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", + "time": "2017-12-08T17:50:26+00:00" + }, + { + "name": "wp-cli/media-command", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/media-command.git", + "reference": "b591fe02ca32f78877300410799a562f7a301d48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/media-command/zipball/b591fe02ca32f78877300410799a562f7a301d48", + "reference": "b591fe02ca32f78877300410799a562f7a301d48", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "media", + "media import", + "media regenerate", + "media image-size" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "media-command.php" + ] + }, + "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", + "time": "2017-12-15T15:00:38+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": { + "psr-4": { + "Mustangostang\\": "src/" + }, + "files": [ + "includes/functions.php" + ] + }, + "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/", + "time": "2017-04-25T11:26:20+00:00" + }, + { + "name": "wp-cli/package-command", + "version": "v1.0.10", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/package-command.git", + "reference": "b7d2ee2fc0f4ff052799fdc4308ac2f99031f66a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/package-command/zipball/b7d2ee2fc0f4ff052799fdc4308ac2f99031f66a", + "reference": "b7d2ee2fc0f4ff052799fdc4308ac2f99031f66a", + "shasum": "" + }, + "require": { + "composer/composer": "^1.2.0", + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "package", + "package browse", + "package install", + "package list", + "package update", + "package uninstall" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "package-command.php" + ] + }, + "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", + "time": "2017-12-08T15:46:21+00:00" + }, + { + "name": "wp-cli/php-cli-tools", + "version": "v0.11.8", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/php-cli-tools.git", + "reference": "363c75349f5dde561e0b416dd00f7aaa76fa2c27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/363c75349f5dde561e0b416dd00f7aaa76fa2c27", + "reference": "363c75349f5dde561e0b416dd00f7aaa76fa2c27", + "shasum": "" + }, + "require": { + "php": ">= 5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "cli": "lib/" + }, + "files": [ + "lib/cli/cli.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" + }, + { + "name": "Daniel Bachhuber", + "email": "daniel@handbuilt.co", + "role": "Maintainer" + } + ], + "description": "Console utilities for PHP", + "homepage": "http://github.com/wp-cli/php-cli-tools", + "keywords": [ + "cli", + "console" + ], + "time": "2017-10-12T21:50:48+00:00" + }, + { + "name": "wp-cli/rewrite-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/rewrite-command.git", + "reference": "6b1695887e289ffad14c8f4ea86b5f1d92757408" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/rewrite-command/zipball/6b1695887e289ffad14c8f4ea86b5f1d92757408", + "reference": "6b1695887e289ffad14c8f4ea86b5f1d92757408", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "rewrite", + "rewrite flush", + "rewrite list", + "rewrite structure" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "rewrite-command.php" + ] + }, + "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", + "time": "2017-12-08T17:51:04+00:00" + }, + { + "name": "wp-cli/role-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/role-command.git", + "reference": "be7f8ea91922864d0c75e45953fbe41d44bebb25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/role-command/zipball/be7f8ea91922864d0c75e45953fbe41d44bebb25", + "reference": "be7f8ea91922864d0c75e45953fbe41d44bebb25", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "role", + "role create", + "role delete", + "role exists", + "role list", + "role reset", + "cap", + "cap add", + "cap list", + "cap remove" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "role-command.php" + ] + }, + "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", + "time": "2017-12-08T17:51:40+00:00" + }, + { + "name": "wp-cli/scaffold-command", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/scaffold-command.git", + "reference": "24b4ddeeb9531179921172597064c8f54f9f594d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/scaffold-command/zipball/24b4ddeeb9531179921172597064c8f54f9f594d", + "reference": "24b4ddeeb9531179921172597064c8f54f9f594d", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "scaffold", + "scaffold _s", + "scaffold block", + "scaffold child-theme", + "scaffold plugin", + "scaffold plugin-tests", + "scaffold post-type", + "scaffold taxonomy", + "scaffold theme-tests" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "scaffold-command.php" + ] + }, + "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", + "time": "2017-12-18T14:24:43+00:00" + }, + { + "name": "wp-cli/search-replace-command", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/search-replace-command.git", + "reference": "813977a75e915581a93f0cb931c325f9a231a2e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/search-replace-command/zipball/813977a75e915581a93f0cb931c325f9a231a2e9", + "reference": "813977a75e915581a93f0cb931c325f9a231a2e9", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "search-replace" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "search-replace-command.php" + ] + }, + "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", + "time": "2017-12-11T15:35:16+00:00" + }, + { + "name": "wp-cli/server-command", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/server-command.git", + "reference": "6192e6d7becd07e4c11a8f1560655c73a3b3526a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/server-command/zipball/6192e6d7becd07e4c11a8f1560655c73a3b3526a", + "reference": "6192e6d7becd07e4c11a8f1560655c73a3b3526a", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "server" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "server-command.php" + ] + }, + "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", + "time": "2017-12-14T20:06:24+00:00" + }, + { + "name": "wp-cli/shell-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/shell-command.git", + "reference": "507603a8994d984b6c4d5bd26e31ede6d9cce37e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/shell-command/zipball/507603a8994d984b6c4d5bd26e31ede6d9cce37e", + "reference": "507603a8994d984b6c4d5bd26e31ede6d9cce37e", + "shasum": "" + }, + "require": { + "wp-cli/wp-cli": "*" + }, + "require-dev": { + "behat/behat": "~2.5" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "shell" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/", + "WP_CLI\\": "src/WP_CLI" + }, + "files": [ + "shell-command.php" + ] + }, + "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", + "time": "2017-12-08T16:03:53+00:00" + }, + { + "name": "wp-cli/super-admin-command", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/super-admin-command.git", + "reference": "2982d2e6514dbb318561d72d0577746a3a37181e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/super-admin-command/zipball/2982d2e6514dbb318561d72d0577746a3a37181e", + "reference": "2982d2e6514dbb318561d72d0577746a3a37181e", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "super-admin", + "super-admin add", + "super-admin list", + "super-admin remove" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "super-admin-command.php" + ] + }, + "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", + "time": "2017-12-08T17:43:53+00:00" + }, + { + "name": "wp-cli/widget-command", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/wp-cli/widget-command.git", + "reference": "657e0f77d80c892f8f72f90a3a25112c254386df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wp-cli/widget-command/zipball/657e0f77d80c892f8f72f90a3a25112c254386df", + "reference": "657e0f77d80c892f8f72f90a3a25112c254386df", + "shasum": "" + }, + "require-dev": { + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" + }, + "type": "wp-cli-package", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "commands": [ + "widget", + "widget add", + "widget deactivate", + "widget delete", + "widget list", + "widget move", + "widget reset", + "widget update", + "sidebar", + "sidebar list" + ], + "bundled": true + }, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ + "widget-command.php" + ] + }, + "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", + "time": "2017-12-08T17:45:57+00:00" + } + ], + "packages-dev": [ + { + "name": "behat/behat", + "version": "v2.5.5", + "source": { + "type": "git", + "url": "https://github.com/Behat/Behat.git", + "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Behat/zipball/c1e48826b84669c97a1efa78459aedfdcdcf2120", + "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120", + "shasum": "" + }, + "require": { + "behat/gherkin": "~2.3.0", + "php": ">=5.3.1", + "symfony/config": "~2.3", + "symfony/console": "~2.0", + "symfony/dependency-injection": "~2.0", + "symfony/event-dispatcher": "~2.0", + "symfony/finder": "~2.0", + "symfony/translation": "~2.3", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.19" + }, + "suggest": { + "behat/mink-extension": "for integration with Mink testing framework", + "behat/symfony2-extension": "for integration with Symfony2 web framework", + "behat/yii-extension": "for integration with Yii web framework" + }, + "bin": [ + "bin/behat" + ], + "type": "library", + "autoload": { + "psr-0": { + "Behat\\Behat": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Scenario-oriented BDD framework for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Symfony2" + ], + "time": "2015-06-01T09:37:55+00:00" + }, + { + "name": "behat/gherkin", + "version": "v2.3.5", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "2b33963da5525400573560c173ab5c9c057e1852" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2b33963da5525400573560c173ab5c9c057e1852", + "reference": "2b33963da5525400573560c173ab5c9c057e1852", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/finder": "~2.0" + }, + "require-dev": { + "symfony/config": "~2.0", + "symfony/translation": "~2.0", + "symfony/yaml": "~2.0" + }, + "suggest": { + "symfony/config": "If you want to use Config component to manage resources", + "symfony/translation": "If you want to use Symfony2 translations adapter", + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "DSL", + "Symfony2", + "parser" + ], + "time": "2013-10-15T11:22:17+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.4.4", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0", + "php": "^5.3|^7", + "squizlabs/php_codesniffer": "*" + }, + "require-dev": { + "composer/composer": "*", + "wimg/php-compatibility": "^8.0" + }, + "suggest": { + "dealerdirect/qa-tools": "All the PHP QA tools you'll need" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "f.nijhof@dealerdirect.nl", + "homepage": "http://workingatdealerdirect.eu", + "role": "Developer" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://workingatdealerdirect.eu", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "time": "2017-12-06T16:27:17+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "1.2.18", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.2.0@stable", + "phpunit/php-token-stream": ">=1.1.3,<1.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2014-09-02T10:13:14+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2014-03-03T05:10:30+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.38", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.1", + "phpunit/php-timer": "~1.0", + "phpunit/phpunit-mock-objects": "~1.2", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear.php.net/pear": "1.9.4" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2014-10-17T09:04:17+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2013-01-13T10:24:48+00:00" + }, + { + "name": "roave/security-advisories", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "0703d860f83775029738dc4a519301e8e3d81853" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0703d860f83775029738dc4a519301e8e3d81853", + "reference": "0703d860f83775029738dc4a519301e8e3d81853", + "shasum": "" + }, + "conflict": { + "adodb/adodb-php": "<5.20.6", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "aws/aws-sdk-php": ">=3,<3.2.1", + "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "codeigniter/framework": "<=3.0.6", + "composer/composer": "<=1.0.0-alpha11", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/core": ">=2,<3.5.31", + "contao/core-bundle": ">=4,<4.4.8", + "contao/listing-bundle": ">=4,<4.4.8", + "doctrine/annotations": ">=1,<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<=0.7.1", + "doctrine/mongodb-odm": ">=1,<1.0.2", + "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", + "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", + "dompdf/dompdf": ">=0.6,<0.6.2", + "drupal/core": ">=8,<8.3.7", + "drupal/drupal": ">=8,<8.3.7", + "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.2|>=5.4,<5.4.10.1|>=2017.8,<2017.8.1.1", + "firebase/php-jwt": "<2", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "gree/jose": "<=2.2", + "gregwar/rst": "<1.0.3", + "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1", + "illuminate/auth": ">=4,<4.0.99|>=4.1,<4.1.26", + "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", + "joomla/session": "<1.3.1", + "laravel/framework": ">=4,<4.0.99|>=4.1,<4.1.29", + "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "magento/magento1ce": ">=1.5.0.1,<1.9.3.2", + "magento/magento1ee": ">=1.9,<1.14.3.2", + "magento/magento2ce": ">=2,<2.2", + "monolog/monolog": ">=1.8,<1.12", + "namshi/jose": "<2.2", + "onelogin/php-saml": "<2.10.4", + "oro/crm": ">=1.7,<1.7.4", + "oro/platform": ">=1.7,<1.7.4", + "phpmailer/phpmailer": ">=5,<5.2.24", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpxmlrpc/extras": "<6.0.1", + "pusher/pusher-php-server": "<2.2.1", + "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", + "shopware/shopware": "<5.2.25", + "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": ">=3,<3.3", + "silverstripe/userforms": "<3", + "simplesamlphp/saml2": "<1.8.1|>=1.9,<1.9.1|>=1.10,<1.10.3|>=2,<2.3.3", + "simplesamlphp/simplesamlphp": "<1.14.16", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "socalnick/scn-social-auth": "<1.15.2", + "squizlabs/php_codesniffer": ">=1,<2.8.1", + "swiftmailer/swiftmailer": ">=4,<5.4.5", + "symfony/dependency-injection": ">=2,<2.0.17", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", + "symfony/http-foundation": ">=2,<2.3.27|>=2.4,<2.5.11|>=2.6,<2.6.6", + "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/routing": ">=2,<2.0.19", + "symfony/security": ">=2,<2.0.25|>=2.1,<2.1.13|>=2.2,<2.2.9|>=2.3,<2.3.37|>=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8.23,<2.8.25|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.6|>=2.8.23,<2.8.25|>=3,<3.0.6|>=3.2.10,<3.2.12|>=3.3.3,<3.3.5", + "symfony/security-csrf": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/serializer": ">=2,<2.0.11", + "symfony/symfony": ">=2,<2.3.41|>=2.4,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/translation": ">=2,<2.0.17", + "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", + "twig/twig": "<1.20", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.22|>=8,<8.7.5", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", + "willdurand/js-translation-bundle": "<2.1.1", + "yiisoft/yii": ">=1.1.14,<1.1.15", + "yiisoft/yii2": "<2.0.5", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<2.0.4", + "yiisoft/yii2-gii": "<2.0.4", + "yiisoft/yii2-jui": "<2.0.4", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-diactoros": ">=1,<1.0.4", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.3,<2.3.8|>=2.4,<2.4.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": ">=2,<2.4.11|>=2.5,<2.5.1", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": ">=2,<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "time": "2017-12-24T19:52:27+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62", + "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-05-22T02:43:20+00:00" + }, + { + "name": "wimg/php-compatibility", + "version": "8.0.1", + "source": { + "type": "git", + "url": "https://github.com/wimg/PHPCompatibility.git", + "reference": "4c4385fb891dff0501009670f988d4fe36785249" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/wimg/PHPCompatibility/zipball/4c4385fb891dff0501009670f988d4fe36785249", + "reference": "4c4385fb891dff0501009670f988d4fe36785249", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.2 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "PHPCompatibility\\": "PHPCompatibility/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Wim Godden", + "role": "lead" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2017-08-07T19:39:05+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "0.13.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git", + "reference": "1f64b1a0b5b789822d0303436ee4e30e0135e4dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/1f64b1a0b5b789822d0303436ee4e30e0135e4dc", + "reference": "1f64b1a0b5b789822d0303436ee4e30e0135e4dc", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "time": "2017-08-05T16:08:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "roave/security-advisories": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.29" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.3.29" + } +} diff --git a/features/aliases.feature b/features/aliases.feature index 2cb65c105..22569faea 100644 --- a/features/aliases.feature +++ b/features/aliases.feature @@ -1,6 +1,6 @@ Feature: Create shortcuts to specific WordPress installs - Scenario: Alias for a path to a specific WP installation + Scenario: Alias for a path to a specific WP install Given a WP installation in 'foo' And I run `mkdir bar` And a wp-cli.yml file: @@ -12,14 +12,14 @@ Feature: Create shortcuts to specific WordPress installs When I try `wp core is-installed` Then STDERR should contain: """ - Error: This does not seem to be a WordPress installation. + Error: This does not seem to be a WordPress install. """ And the return code should be 1 When I run `wp @foo core is-installed` Then the return code should be 0 - When I run `wp @foo core is-installed` from 'bar' + When I run `cd bar; wp @foo core is-installed` Then the return code should be 0 Scenario: Error when invalid alias provided @@ -28,7 +28,7 @@ Feature: Create shortcuts to specific WordPress installs When I try `wp @test option get home` Then STDERR should be: """ - Error: Alias 'test' not found. + Error: Alias '@test' not found. """ Scenario: Provide suggestion when invalid alias is provided @@ -42,8 +42,8 @@ Feature: Create shortcuts to specific WordPress installs When I try `wp @test option get home` Then STDERR should be: """ - Error: Alias 'test' not found. - Did you mean 'test2'? + Error: Alias '@test' not found. + Did you mean '@test2'? """ Scenario: Treat global params as local when included in alias @@ -57,7 +57,7 @@ Feature: Create shortcuts to specific WordPress installs When I run `wp @foo option get home` Then STDOUT should be: """ - https://example.com + http://example.com """ When I try `wp @foo option get home --path=foo` @@ -70,7 +70,7 @@ Feature: Create shortcuts to specific WordPress installs unknown --path parameter """ - When I run `wp @foo eval "echo get_current_user_id();" --user=admin` + When I run `wp @foo eval 'echo get_current_user_id();' --user=admin` Then STDOUT should be: """ 1 @@ -83,13 +83,13 @@ Feature: Create shortcuts to specific WordPress installs user: admin """ - When I run `wp @foo eval "echo get_current_user_id();"` + When I run `wp @foo eval 'echo get_current_user_id();'` Then STDOUT should be: """ 1 """ - When I try `wp @foo eval "echo get_current_user_id();" --user=admin` + When I try `wp @foo eval 'echo get_current_user_id();' --user=admin` Then STDERR should contain: """ Parameter errors: @@ -111,7 +111,7 @@ Feature: Create shortcuts to specific WordPress installs When I run `wp @foo option get home` Then STDOUT should be: """ - https://example.com + http://example.com """ And STDERR should be empty @@ -123,10 +123,10 @@ Feature: Create shortcuts to specific WordPress installs path: foo """ - When I run `wp eval --skip-wordpress "echo \WP_CLI\Path::normalize( DIRECTORY_SEPARATOR === '/' ? realpath( getcwd() ) : getcwd() );"` + When I run `wp eval --skip-wordpress 'echo realpath( getenv( "RUN_DIR" ) );'` Then save STDOUT as {TEST_DIR} - When I run `wp cli alias list` + When I run `wp cli alias` Then STDOUT should be YAML containing: """ @all: Run command against every registered alias. @@ -142,902 +142,116 @@ Feature: Create shortcuts to specific WordPress installs path: {TEST_DIR}/foo """ - When I run `wp cli alias list --format=json` + When I run `wp cli alias --format=json` Then STDOUT should be JSON containing: """ {"@all":"Run command against every registered alias.","@foo":{"path":"{TEST_DIR}/foo"}} """ - When I run `wp cli aliases --format=json` - Then STDOUT should be JSON containing: - """ - {"@all":"Run command against every registered alias.","@foo":{"path":"{TEST_DIR}/foo"}} - """ - - Scenario: Get alias information - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - """ - - When I run `wp cli alias get @foo` - Then STDOUT should be: - """ - ssh: user@host:/path/to/wordpress - """ - - When I try `wp cli alias get @someotherfoo` - Then STDERR should be: - """ - Error: No alias found with key '@someotherfoo'. - """ - - When I run `wp cli alias get @foo --field=ssh` - Then STDOUT should be: - """ - user@host:/path/to/wordpress - """ - - When I try `wp cli alias get @foo --field=user` - Then STDERR should be: - """ - Error: The 'user' property does not exist for '@foo'. - """ - - @skip-windows @skip-macos - Scenario: Adds proxyjump to ssh command - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - proxyjump: proxyhost - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -J 'proxyhost' -T -vvv - """ - - @skip-windows @skip-macos - Scenario: Adds key to ssh command - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - key: identityfile.key - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -i 'identityfile.key' -T -vvv - """ - - @skip-windows @skip-macos - Scenario: Adds ssh_config to ssh command - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - ssh_config: /path/to/ssh/config - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -F '/path/to/ssh/config' -T -vvv - """ - - @skip-windows @skip-macos - Scenario: Vagrant SSH disables strict host key checking - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: vagrant - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -T -vvv '' 'wp --debug --version' - """ - - @skip-windows @skip-macos - Scenario: SSH alias expands tilde in path - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:~/sites/example.com/www - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - 'cd ~/ - """ - And STDERR should contain: - """ - sites/example.com/www - """ - - @skip-windows @skip-macos - Scenario: Connection-specific properties are not passed to remote WP-CLI - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - proxyjump: proxyhost - key: identityfile.key - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -J 'proxyhost' -i 'identityfile.key' -T -vvv - """ - And STDERR should not contain: - """ - WP_CLI_RUNTIME_ALIAS - """ - - @skip-windows @skip-macos - Scenario: WordPress-specific properties are passed to remote WP-CLI - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - user: admin - path: /var/www/html - """ - - When I try `wp @foo --debug core version` - Then STDERR should contain: - """ - WP_CLI_RUNTIME_ALIAS - """ - And STDERR should contain: - """ - @foo - """ - - @skip-windows @skip-macos - Scenario: SSH commands should not be double-escaped - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - """ - - When I try `wp @foo plugin list --debug` - Then STDERR should contain: - """ - Running SSH command: ssh -T -vvv 'user@host' 'cd '\''/path/to/wordpress'\''; wp plugin list --debug' - """ - - @skip-windows @skip-macos - Scenario: SSH commands correctly escape arguments with spaces - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host:/path/to/wordpress - """ - - When I try `wp @foo post create --post_title=My Title --debug` - Then STDERR should contain: - """ - Running SSH command: ssh -T -vvv 'user@host' 'cd '\''/path/to/wordpress'\''; wp post create --post_title=My Title - """ - - @skip-windows @skip-macos - Scenario: Uses env command for runtime alias with separate path line - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host - path: /path/to/wordpress - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -T -vvv 'user@host' 'env WP_CLI_RUNTIME_ALIAS= - """ - And STDERR should contain: - """ - wp @foo - """ - - @skip-windows @skip-macos - Scenario: Two aliases with same SSH host but different paths generate different remote commands - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @prod: - ssh: user@host - path: /path/to/production - @staging: - ssh: user@host - path: /path/to/staging - """ - - When I try `wp @prod --debug --version` - Then STDERR should contain: - """ - \/path\/to\/production - """ - And STDERR should not contain: - """ - \/path\/to\/staging - """ - - When I try `wp @staging --debug --version` - Then STDERR should contain: - """ - \/path\/to\/staging - """ - And STDERR should not contain: - """ - \/path\/to\/production - """ - - @skip-windows @skip-macos - Scenario: Properly escapes single quotes in runtime alias path - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: user@host - path: /path/to/user's/wordpress - """ - - When I try `wp @foo --debug --version` - Then STDERR should contain: - """ - Running SSH command: ssh -T -vvv 'user@host' 'env WP_CLI_RUNTIME_ALIAS= - """ - And STDERR should contain: - """ - \/path\/to\/user'\''\'\'''\''s\/wordpress - """ - - Scenario: Add an alias - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: wpcli@wp-cli.org:2222 - """ - - When I run `wp cli alias add @dev --set-user=wpcli --set-path=/path/to/wordpress --config=project` - Then STDOUT should be: - """ - Success: Added 'dev' alias. - """ - When I run `wp cli alias list` - Then STDOUT should be YAML containing: - """ - @all: Run command against every registered alias. - @foo: - ssh: wpcli@wp-cli.org:2222 - @dev: - user: wpcli - path: /path/to/wordpress - """ - - When I try `wp cli alias add @something --config=project` - Then STDERR should be: - """ - Error: No valid arguments passed. - """ - - When I try `wp cli alias add @something --set-user= --config=project` - Then STDERR should be: - """ - Error: No value passed to arguments. - """ - - When I try `wp cli alias add @something --set-path=/new/path --grouping=foo,dev --config=project` - Then STDERR should be: - """ - Error: --grouping argument works alone. Found invalid arg(s) 'set-path'. - """ - - Scenario: Delete an alias - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - ssh: foo@bar:/path/to/wordpress - @dev: - ssh: user@hostname:/path/to/wordpress - """ - - When I run `wp cli alias delete @dev --config=project` - Then STDOUT should be: - """ - Success: Deleted 'dev' alias. - """ - When I run `wp cli alias list` - Then STDOUT should be YAML containing: - """ - @all: Run command against every registered alias. - @foo: - ssh: foo@bar:/path/to/wordpress - """ - When I try `wp cli alias delete @dev` - Then STDERR should be: - """ - Error: No alias found with key '@dev'. - """ - - When I try `wp cli alias update @foo` - Then STDERR should be: - """ - Error: No valid arguments passed. - """ - - Scenario: Update an alias - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - user: wpcli - @foopath: - path: /home/wpcli/sites/wpcli - @foogroup: - - @foo - - @foopath - """ - - When I run `wp cli alias update @foo --set-user=newuser --config=project` - Then STDOUT should be: - """ - Success: Updated 'foo' alias. - """ - When I run `wp cli alias list` - Then STDOUT should be YAML containing: - """ - @all: Run command against every registered alias. - @foo: - user: newuser - @foopath: - path: /home/wpcli/sites/wpcli - @foogroup: - - @foo - - @foopath - """ - When I try `wp cli alias update @otherfoo --set-ssh=foo@host --set-path=/some/path` - Then STDERR should be: - """ - Error: No alias found with key '@otherfoo'. - """ - - When I try `wp cli alias update @foogroup --set-ssh=foo@host` - Then STDERR should be: - """ - Error: Trying to update group alias with invalid arguments. - """ - - When I try `wp cli alias update @foo --grouping=foo@host --set-user=wpcli` - Then STDERR should be: - """ - Error: --grouping argument works alone. Found invalid arg(s) 'set-user'. - """ - - When I try `wp cli alias update @foo --grouping=foo@host` - Then STDERR should be: - """ - Error: Trying to update simple alias with invalid --grouping argument. - """ - - When I try `wp cli alias update @foo --set-path=/new/path` - Then STDOUT should be: - """ - Success: Updated 'foo' alias. - """ - - When I run `wp cli alias list` - Then STDOUT should be YAML containing: - """ - @all: Run command against every registered alias. - @foo: - user: newuser - path: /new/path - @foopath: - path: /home/wpcli/sites/wpcli - @foogroup: - - @foo - - @foopath - """ - - Scenario: Defining a project alias completely overrides a global alias - Given a WP installation in 'foo' - And a config.yml file: - """ - @foo: - path: foo - """ - - When I run `WP_CLI_CONFIG_PATH=config.yml wp @foo option get home` - Then STDOUT should be: - """ - https://example.com - """ - - Given a wp-cli.yml file: - """ - @foo: - path: none-existent-install - """ - When I try `WP_CLI_CONFIG_PATH=config.yml wp @foo option get home` - Then STDERR should contain: - """ - Error: This does not seem to be a WordPress installation. - """ - - # TODO: Investigate on Windows why `@bar` is missing from @both output. - @skip-windows - Scenario: Use a group of aliases to run a command against multiple installs - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: - """ - @both: - - @foo - - @bar - @invalid: - - @foo - - @baz - @foo: - path: foo - @bar: - path: bar - """ - - When I run `wp @foo option update home "http://apple.com"` - And I run `wp @foo option get home` - Then STDOUT should contain: - """ - http://apple.com - """ - - When I run `wp @bar option update home "http://google.com"` - And I run `wp @bar option get home` - Then STDOUT should contain: - """ - http://google.com - """ - - When I try `wp @invalid option get home` - Then STDERR should be: - """ - Error: Group '@invalid' contains one or more invalid aliases: @baz - """ - - When I run `wp @both option get home` - Then STDOUT should be: - """ - @foo - http://apple.com - @bar - http://google.com - """ - - When I run `wp @both option get home --quiet` - Then STDOUT should be: - """ - http://apple.com - http://google.com - """ - - # TODO: Investigate on Windows why `@bar` is missing from @all output. - @skip-windows - Scenario: Register '@all' alias for running on one or more aliases - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: - """ - @foo: - path: foo - @bar: - path: bar - """ - - When I run `wp @foo option update home "http://apple.com"` - And I run `wp @foo option get home` - Then STDOUT should contain: - """ - http://apple.com - """ - - When I run `wp @bar option update home "http://google.com"` - And I run `wp @bar option get home` - Then STDOUT should contain: - """ - http://google.com - """ - - When I run `wp @all option get home` - Then STDOUT should be: - """ - @foo - http://apple.com - @bar - http://google.com - """ - - When I run `wp @all option get home --quiet` - Then STDOUT should be: - """ - http://apple.com - http://google.com - """ - - Scenario: Don't register '@all' when its already set - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: - """ - @all: - path: foo - @bar: - path: bar - """ - - When I run `wp @all option get home | wc -l | tr -d ' '` - Then STDOUT should be: - """ - 1 - """ - - Scenario: Error when '@all' is used without aliases defined - Given an empty directory - - When I try `wp @all option get home` - Then STDERR should be: - """ - Error: Cannot use 'all' when no aliases are registered. - """ - - Scenario: Alias for a subsite of a multisite install - Given a WP multisite subdomain installation - And a wp-cli.yml file: - """ - url: https://example.com - @subsite: - url: https://subsite.example.com - """ - - When I run `wp site create --slug=subsite` - Then STDOUT should not be empty - - When I run `wp option get siteurl` - Then STDOUT should be: - """ - https://example.com - """ - - # TODO: The HTTPS default is currently not forwarded to subsite creation. - When I run `wp @subsite option get siteurl` - Then STDOUT should be: - """ - http://subsite.example.com - """ - - When I try `wp @subsite option get siteurl --url=subsite.example.com` - Then STDERR should be: - """ - Error: Parameter errors: - unknown --url parameter - """ - - # TODO: Investigate on Windows why `@bar` is missing from @foobar output. - @skip-windows - Scenario: Global parameters should be passed to grouped aliases - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: - """ - @foo: - path: foo - @bar: - path: bar - @foobar: - - @foo - - @bar - """ - - When I try `wp core is-installed --allow-root --debug` - Then STDERR should contain: - """ - Error: This does not seem to be a WordPress installation. - """ - And STDERR should contain: - """ - core is-installed --allow-root --debug - """ - And the return code should be 1 - - When I try `wp @foo core is-installed --allow-root --debug` - Then the return code should be 0 - And STDERR should contain: - """ - @foo core is-installed --allow-root --debug - """ - - When I try `wp @bar core is-installed --allow-root --debug` from 'bar' - Then the return code should be 0 - And STDERR should contain: - """ - @bar core is-installed --allow-root --debug - """ - - When I try `wp @foobar core is-installed --allow-root --debug` - Then the return code should be 0 - And STDERR should contain: - """ - @foobar core is-installed --allow-root --debug - """ - And STDERR should contain: - """ - --alias=foo core is-installed --allow-root --debug - """ - And STDERR should contain: - """ - --alias=bar core is-installed --allow-root --debug - """ - - @skip-windows - Scenario Outline: Check that proc_open() and proc_close() aren't disabled for grouped aliases - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: - """ - @foo: - path: foo - @bar: - path: bar - @foobar: - - @foo - - @bar - """ - - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=} @foobar core is-installed` - Then STDERR should contain: - """ - Error: Cannot do 'group alias': The PHP functions `proc_open()` and/or `proc_close()` are disabled - """ - And the return code should be 1 - - Examples: - | func | - | proc_open | - | proc_close | - - Scenario: An alias is a group of aliases - Given a WP install - And a wp-cli.yml file: - """ - @foo: - path: foo - @bar: - path: bar - @both: - - @foo - - @bar - """ - - When I try `wp cli alias is-group @both` - Then the return code should be 0 - - Scenario: An alias is not a group of aliases - Given a WP install - And a wp-cli.yml file: - """ - @foo: - path: foo - """ - - When I try `wp cli alias is-group @foo` - Then the return code should be 1 - - Scenario: Automatically add "@" prefix to an alias - Given a WP install - And a wp-cli.yml file: + Scenario: Defining a project alias completely overrides a global alias + Given a WP installation in 'foo' + And a config.yml file: """ @foo: path: foo """ - When I run `wp cli alias add hello --set-path=/path/to/wordpress` + When I run `WP_CLI_CONFIG_PATH=config.yml wp @foo option get home` Then STDOUT should be: """ - Success: Added 'hello' alias. + http://example.com """ - When I run `wp eval --skip-wordpress "echo \WP_CLI\Path::normalize( DIRECTORY_SEPARATOR === '/' ? realpath( getcwd() ) : getcwd() );"` - Then save STDOUT as {TEST_DIR} - - When I run `wp cli alias list` - Then STDOUT should be YAML containing: + Given a wp-cli.yml file: """ - @all: Run command against every registered alias. - @hello: - path: /path/to/wordpress @foo: - path: {TEST_DIR}/foo - """ - - Scenario: Use environment variables in alias definitions - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @dev: - user: ${env.TEST_WP_USER} - path: ${env.TEST_WP_PATH} - """ - - When I run `TEST_WP_USER=admin TEST_WP_PATH=foo wp @dev eval "echo get_current_user_id();"` - Then STDOUT should be: - """ - 1 - """ - - When I run `TEST_WP_USER=admin TEST_WP_PATH=foo wp @dev option get home` - Scenario: Use alternative aliases syntax without @ prefix - Given a WP installation in 'foo' - And I run `mkdir bar` - And a wp-cli.yml file: - """ - aliases: - foo: - path: foo + path: none-existent-install """ - - When I try `wp core is-installed` + When I try `WP_CLI_CONFIG_PATH=config.yml wp @foo option get home` Then STDERR should contain: """ - Error: This does not seem to be a WordPress installation. + Error: This does not seem to be a WordPress install. """ - And the return code should be 1 - - When I run `wp --alias=foo core is-installed` - Then the return code should be 0 - - When I run `wp --alias=foo core is-installed` from 'bar' - Then the return code should be 0 - Scenario: Mix traditional and new alias syntax + Scenario: Use a group of aliases to run a command against multiple installs Given a WP installation in 'foo' - And a WP installation in 'bar' + And a WP install in 'bar' And a wp-cli.yml file: """ + @both: + - @foo + - @bar + @invalid: + - @foo + - @baz @foo: path: foo - aliases: - bar: - path: bar - """ - - When I run `wp @foo option get home` - Then STDOUT should be: - """ - https://example.com - """ - - When I run `wp --alias=bar option get home` - Then STDOUT should be: - """ - https://example.com - """ - - Scenario: Use environment variables in SSH alias definitions - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @prod: - ssh: ${env.SSH_USER}@${env.SSH_HOST}:${env.SSH_PATH} - """ - - When I run `SSH_USER=admin SSH_HOST=example.com SSH_PATH=/var/www wp cli alias get @prod` - Then STDOUT should be: - """ - ssh: admin@example.com:/var/www + @bar: + path: bar """ - Scenario: Handle missing environment variables gracefully - Given a WP installation in 'foo' - And a wp-cli.yml file: + When I run `wp @foo option update home 'http://apple.com'` + And I run `wp @foo option get home` + Then STDOUT should contain: """ - @test: - path: ${env.NONEXISTENT_VAR}/wordpress + http://apple.com """ - When I run `wp cli alias get @test` + When I run `wp @bar option update home 'http://google.com'` + And I run `wp @bar option get home` Then STDOUT should contain: """ - ${env.NONEXISTENT_VAR} + http://google.com """ - Scenario: View aliases without environment variable interpolation using --raw flag - Given a WP installation in 'foo' - And a wp-cli.yml file: + When I try `wp @invalid option get home` + Then STDERR should be: """ - @dev: - user: ${env.TEST_WP_USER} - path: ${env.TEST_WP_PATH} - @prod: - ssh: ${env.SSH_USER}@${env.SSH_HOST}:${env.SSH_PATH} + Error: Group '@invalid' contains one or more invalid aliases: @baz """ - When I run `TEST_WP_USER=admin TEST_WP_PATH=foo wp cli alias get @dev --raw` + When I run `wp @both option get home` Then STDOUT should be: """ - user: ${env.TEST_WP_USER} - path: ${env.TEST_WP_PATH} + @foo + http://apple.com + @bar + http://google.com """ - When I run `SSH_USER=admin SSH_HOST=example.com SSH_PATH=/var/www wp cli alias get @prod --raw` + When I run `wp @both option get home --quiet` Then STDOUT should be: """ - ssh: ${env.SSH_USER}@${env.SSH_HOST}:${env.SSH_PATH} - """ - - When I run `wp cli alias list --raw --format=json` - Then STDOUT should contain: - """ - ${env.TEST_WP_USER} - """ - And STDOUT should contain: - """ - ${env.SSH_USER} + http://apple.com + http://google.com """ - # TODO: Investigate on Windows why `@bar` is missing from --alias=both output. - @skip-windows - Scenario: Use --alias flag with groups + Scenario: Register '@all' alias for running on one or more aliases Given a WP installation in 'foo' - And a WP installation in 'bar' + And a WP install in 'bar' And a wp-cli.yml file: """ - aliases: - foo: - path: foo - bar: - path: bar - both: - - foo - - bar + @foo: + path: foo + @bar: + path: bar """ - When I run `wp --alias=foo option update home "http://apple.com"` - And I run `wp --alias=foo option get home` + When I run `wp @foo option update home 'http://apple.com'` + And I run `wp @foo option get home` Then STDOUT should contain: """ http://apple.com """ - When I run `wp --alias=bar option update home "http://google.com"` - And I run `wp --alias=bar option get home` + When I run `wp @bar option update home 'http://google.com'` + And I run `wp @bar option get home` Then STDOUT should contain: """ http://google.com """ - When I run `wp --alias=both option get home` + When I run `wp @all option get home` Then STDOUT should be: """ @foo @@ -1046,230 +260,146 @@ Feature: Create shortcuts to specific WordPress installs http://google.com """ - Scenario: List aliases defined with new syntax - Given an empty directory - And a wp-cli.yml file: + When I run `wp @all option get home --quiet` + Then STDOUT should be: """ - aliases: - foo: - path: foo + http://apple.com + http://google.com """ - When I run `wp eval --skip-wordpress "echo \WP_CLI\Path::normalize( DIRECTORY_SEPARATOR === '/' ? realpath( getcwd() ) : getcwd() );"` - Then save STDOUT as {TEST_DIR} + Scenario: Don't register '@all' when its already set + Given a WP installation in 'foo' + And a WP install in 'bar' + And a wp-cli.yml file: + """ + @all: + path: foo + @bar: + path: bar + """ - When I run `wp cli alias list` - Then STDOUT should be YAML containing: + When I run `wp @all option get home | wc -l | tr -d ' '` + Then STDOUT should be: """ - @all: Run command against every registered alias. - @foo: - path: {TEST_DIR}/foo + 1 """ - Scenario: Error when invalid alias provided with --alias flag + Scenario: Error when '@all' is used without aliases defined Given an empty directory - When I try `wp --alias=test option get home` + When I try `wp @all option get home` Then STDERR should be: """ - Error: Alias 'test' not found. + Error: Cannot use '@all' when no aliases are registered. """ - # TODO: Investigate on Windows why `@bar` is missing from --alias=all output. - @skip-windows - Scenario: Backwards compatibility with @all for new syntax - Given a WP installation in 'foo' - And a WP installation in 'bar' + Scenario: Alias for a subsite of a multisite install + Given a WP multisite subdomain installation And a wp-cli.yml file: """ - aliases: - foo: - path: foo - bar: - path: bar - """ - - When I run `wp --alias=foo option update home "http://apple.com"` - And I run `wp --alias=foo option get home` - Then STDOUT should contain: - """ - http://apple.com + url: example.com + @subsite: + url: subsite.example.com """ - When I run `wp --alias=bar option update home "http://google.com"` - And I run `wp --alias=bar option get home` - Then STDOUT should contain: - """ - http://google.com - """ + When I run `wp site create --slug=subsite` + Then STDOUT should not be empty - When I run `wp --alias=all option get home` + When I run `wp option get siteurl` Then STDOUT should be: """ - @foo - http://apple.com - @bar - http://google.com - """ - - Scenario: Mix @prefix and --alias flag with same config - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - aliases: - foo: - path: foo + http://example.com """ - When I run `wp @foo option get home` + When I run `wp @subsite option get siteurl` Then STDOUT should be: """ - https://example.com + http://subsite.example.com """ - When I run `wp --alias=foo option get home` - Then STDOUT should be: + When I try `wp @subsite option get siteurl --url=subsite.example.com` + Then STDERR should be: """ - https://example.com + Error: Parameter errors: + unknown --url parameter """ - Scenario: Run alias groups in parallel with WP_CLI_ALIAS_GROUPS_PARALLEL environment variable + Scenario: Global parameters should be passed to grouped aliases Given a WP installation in 'foo' - And a WP installation in 'bar' + And a WP install in 'bar' And a wp-cli.yml file: """ - @both: - - @foo - - @bar @foo: path: foo @bar: path: bar - env: - WP_CLI_ALIAS_GROUPS_PARALLEL: 1 - """ - - When I run `wp @foo option update home "http://parallel-foo.com"` - And I run `wp @bar option update home "http://parallel-bar.com"` - And I run `wp @both option get home` - Then STDOUT should contain: - """ - @foo - """ - And STDOUT should contain: - """ - http://parallel-foo.com - """ - And STDOUT should contain: - """ - @bar - """ - And STDOUT should contain: - """ - http://parallel-bar.com + @foobar: + - @foo + - @bar """ - When I run `wp @both option get home --quiet` - Then STDOUT should contain: - """ - http://parallel-foo.com - """ - And STDOUT should contain: + When I try `wp core is-installed --allow-root --debug` + Then STDERR should contain: """ - http://parallel-bar.com + Error: This does not seem to be a WordPress install. """ - - # TODO: Investigate on Windows why `@bar` is missing from @all output. - @skip-windows - Scenario: Using --quiet with @all suppresses alias names but still outputs command results - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: + And STDERR should contain: """ - @foo: - path: foo - @bar: - path: bar + core is-installed --allow-root --debug """ + And the return code should be 1 - When I run `wp @all eval "echo 'output-from-alias' . PHP_EOL;"` - Then STDOUT should be: + When I try `wp @foo core is-installed --allow-root --debug` + Then the return code should be 0 + And STDERR should contain: """ - @foo - output-from-alias - @bar - output-from-alias + @foo core is-installed --allow-root --debug """ - When I run `wp @all eval "echo 'output-from-alias' . PHP_EOL;" --quiet` - Then STDOUT should be: + When I try `cd bar; wp @bar core is-installed --allow-root --debug` + Then the return code should be 0 + And STDERR should contain: """ - output-from-alias - output-from-alias + @bar core is-installed --allow-root --debug """ - # TODO: Investigate on Windows why `@bar` is missing from @both output. - @skip-windows - Scenario: STDIN piped to alias group is passed to each alias in the group - Given a WP installation in 'foo' - And a WP installation in 'bar' - And a wp-cli.yml file: - """ - @foo: - path: foo - @bar: - path: bar - @both: - - @foo - - @bar - """ - And a stdin.php file: + When I try `wp @foobar core is-installed --allow-root --debug` + Then the return code should be 0 + And STDERR should contain: """ - } @foobar core is-installed` + Then STDERR should contain: """ - http://bar.example.com + Error: Cannot do 'group alias': The PHP functions `proc_open()` and/or `proc_close()` are disabled """ + And the return code should be 1 + + Examples: + | func | + | proc_open | + | proc_close | diff --git a/features/arg-aliases.feature b/features/arg-aliases.feature deleted file mode 100644 index b6e542a06..000000000 --- a/features/arg-aliases.feature +++ /dev/null @@ -1,353 +0,0 @@ -Feature: Argument aliases support - - Scenario: Short-form alias for a parameter - Given a WP install - And a custom-command.php file: - """ - |n] - * : A number value. - */ - $test_command = function( $args, $assoc_args ) { - if ( isset( $assoc_args['number'] ) ) { - WP_CLI::success( 'number is ' . $assoc_args['number'] ); - } else { - WP_CLI::error( 'number is not set' ); - } - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - - When I run `wp --require=custom-command.php test-alias --number=42` - Then STDOUT should contain: - """ - Success: number is 42 - """ - - When I run `wp --require=custom-command.php test-alias -n=42` - Then STDOUT should contain: - """ - Success: number is 42 - """ - - Scenario: Long-form alias for parameter - Given a WP install - And a custom-command.php file: - """ - |f] - * : Output format. - */ - $test_command = function( $args, $assoc_args ) { - WP_CLI::success( 'format is ' . $assoc_args['format'] ); - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - - When I run `wp --require=custom-command.php test-alias --format=json -f=xml` - Then STDOUT should contain: - """ - Success: format is json - """ - - Scenario: Alias resolves before validation - Given a WP install - And a custom-command.php file: - """ - |t - * : Required type parameter. - */ - $test_command = function( $args, $assoc_args ) { - WP_CLI::success( 'type is ' . $assoc_args['type'] ); - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - - When I try `wp --require=custom-command.php test-alias` - Then STDERR should contain: - """ - missing --type parameter - """ - And the return code should be 1 - - When I run `wp --require=custom-command.php test-alias --type=post` - Then STDOUT should contain: - """ - Success: type is post - """ - - When I run `wp --require=custom-command.php test-alias -t=post` - Then STDOUT should contain: - """ - Success: type is post - """ - - Scenario: Aliases are shown in help output - Given a WP install - And a custom-command.php file: - """ - |f] - * : Output format. - */ - $test_command = function( $args, $assoc_args ) {}; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - - When I run `wp --require=custom-command.php help test-alias` - Then STDOUT should contain: - """ - [--verbose|v|wordy] - """ - And STDOUT should contain: - """ - [--format=|f] - """ - - Scenario: Prompting for required parameter with alias (value spec on canonical) - Given a WP install - And a custom-command.php file: - """ - |t - * : Required type parameter. - */ - $test_command = function( $args, $assoc_args ) { - WP_CLI::success( 'type is ' . $assoc_args['type'] ); - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - And a session file: - """ - post - """ - - When I run `wp --require=custom-command.php test-alias --prompt=type < session` - Then STDOUT should contain: - """ - Success: type is post - """ - - Scenario: Prompting for required parameter with alias (value spec on alias) - Given a WP install - And a custom-command.php file: - """ - - * : Admin password. - */ - $test_command = function( $args, $assoc_args ) { - WP_CLI::success( 'password is ' . $assoc_args['admin_password'] ); - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - And a session file: - """ - wpcli - """ - - When I run `wp --require=custom-command.php test-alias --prompt=admin_password < session` - Then STDOUT should contain: - """ - Success: password is wpcli - """ - - Scenario: Prompting using alias name - Given a WP install - And a custom-command.php file: - """ - |t - * : Required type parameter. - */ - $test_command = function( $args, $assoc_args ) { - WP_CLI::success( 'type is ' . $assoc_args['type'] ); - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - And a session file: - """ - page - """ - - When I run `wp --require=custom-command.php test-alias --prompt=t < session` - Then STDOUT should contain: - """ - Success: type is page - """ - - Scenario: Passing alias on command line bypasses prompt - Given a WP install - And a custom-command.php file: - """ - |t - * : Required type parameter. - */ - $test_command = function( $args, $assoc_args ) { - WP_CLI::success( 'type is ' . $assoc_args['type'] ); - }; - WP_CLI::add_command( 'test-alias', $test_command ); - """ - - When I run `wp --require=custom-command.php test-alias -t=post --prompt=type` - Then STDOUT should contain: - """ - Success: type is post - """ - diff --git a/features/autoloader-priority.feature b/features/autoloader-priority.feature deleted file mode 100644 index c8031e700..000000000 --- a/features/autoloader-priority.feature +++ /dev/null @@ -1,94 +0,0 @@ -Feature: Framework autoloader takes priority over package autoloaders - - Scenario: Verify framework autoloader is prepended - Given an empty directory - And a test-command.php file: - """ - $loader ) { - if ( is_array( $loader ) && isset( $loader[0] ) ) { - $class = is_object( $loader[0] ) ? get_class( $loader[0] ) : $loader[0]; - $method = isset( $loader[1] ) ? $loader[1] : ''; - WP_CLI::log( "Autoloader {$index}: {$class}::{$method}" ); - - // Track first WP_CLI autoloader position - if ( $first_wpcli_position === null && $class === 'WP_CLI\\Autoloader' ) { - $first_wpcli_position = $index; - } - } - } - - if ( $first_wpcli_position === null ) { - WP_CLI::error( "WP_CLI\\Autoloader not found in registered autoloaders" ); - } - - // Verify the WP_CLI autoloader is in an early position (0, 1, or 2). - // It's expected to be at or near the start of the autoloader queue to ensure it has priority. - if ( $first_wpcli_position <= 2 ) { - WP_CLI::success( "WP_CLI\\Autoloader is at position {$first_wpcli_position} (early in the queue)" ); - } else { - WP_CLI::error( "WP_CLI\\Autoloader is at position {$first_wpcli_position}, should be in first 3 positions!" ); - } - } - } - WP_CLI::add_command( 'test-autoloader', 'Test_Autoloader_Command' ); - """ - - When I run `wp --require=test-command.php test-autoloader check` - Then STDOUT should contain: - """ - early in the queue - """ - And STDOUT should contain: - """ - Success: - """ - - Scenario: Old framework class should not break cmd-dump - Given a WP installation - And a wp-content/old-dispatcher/WP_CLI/Dispatcher/RootCommand.php file: - """ - &1` - - When I run `wp cli cmd-dump` - Then STDOUT should contain: - """ - "name":"wp" - """ - And STDERR should not contain: - """ - OLD RootCommand loaded - """ diff --git a/features/bootstrap.feature b/features/bootstrap.feature index ff0d05f30..8869736a8 100644 --- a/features/bootstrap.feature +++ b/features/bootstrap.feature @@ -1,13 +1,6 @@ Feature: Bootstrap WP-CLI - Background: - When I run `wp package path` - And save STDOUT as {PACKAGE_PATH} - And I run `rm -rf {PACKAGE_PATH}/vendor` - And I run `rm -rf {PACKAGE_PATH}/composer.json` - And I run `rm -rf {PACKAGE_PATH}/composer.lock` - - @less-than-php-7.4 @require-opcache-save-comments + @require-opcache-save-comments Scenario: Basic Composer stack Given an empty directory And a composer.json file: @@ -16,7 +9,7 @@ Feature: Bootstrap WP-CLI "name": "wp-cli/composer-test", "type": "project", "require": { - "wp-cli/wp-cli": "2.11.0" + "wp-cli/wp-cli": "1.1.0" } } """ @@ -26,11 +19,11 @@ Feature: Bootstrap WP-CLI When I run `vendor/bin/wp cli version` Then STDOUT should contain: """ - WP-CLI 2.11.0 + WP-CLI 1.1.0 """ Scenario: Composer stack with override requirement before WP-CLI - Given a WP installation + Given an empty directory And a composer.json file: """ { @@ -41,325 +34,193 @@ Feature: Bootstrap WP-CLI "repositories": [ { "type": "path", - "url": "./override", + "url": "./cli-override-command", "options": { "symlink": false } } ], "require": { - "wp-cli/override": "*", - "wp-cli/wp-cli": "dev-main" + "wp-cli/cli-override-command": "*", + "wp-cli/wp-cli": "dev-master" } } """ - And a override/override.php file: + And a cli-override-command/cli.php file: """ 'before_wp_load' ) ); + $autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; + if ( file_exists( $autoload ) && ! class_exists( 'CLI_Command' ) ) { + require_once $autoload; + } + WP_CLI::add_command( 'cli', 'CLI_Command', array( 'when' => 'before_wp_load' ) ); """ - And a override/src/Custom_Eval_Command.php file: + And a cli-override-command/src/CLI_Command.php file: """ &1` - When I run `vendor/bin/wp eval "\WP_CLI::Success( 'WP-Standard-Eval' );"` + When I run `vendor/bin/wp cli version` Then STDOUT should contain: """ - Success: WP-Override-Eval + Success: WP-Override-CLI """ Scenario: Override command bundled with current source - Given a WP installation - And a override/override.php file: + Given an empty directory + And a cli-override-command/cli.php file: """ 'before_wp_load' ) ); - // Override bundled command. - WP_CLI::add_hook( - 'after_add_command:eval', - static function () { - static $added = false; - if ( ! $added ) { - $added = true; - WP_CLI::add_command( 'eval', 'Custom_Eval_Command', array( 'when' => 'before_wp_load' ) ); - } - } - ); + WP_CLI::add_command( 'cli', 'CLI_Command', array( 'when' => 'before_wp_load' ) ); """ - And a override/src/Custom_CLI_Command.php file: + And a cli-override-command/src/CLI_Command.php file: """ &1` + And I run `composer install --working-dir={RUN_DIR}/cli-override-command --no-interaction 2>&1` When I run `wp cli version` - Then STDOUT should contain: - """ - WP-CLI - """ + Then STDOUT should contain: + """ + WP-CLI + """ - When I run `wp eval "\WP_CLI::Success( 'WP-Standard-Eval' );"` - Then STDOUT should contain: - """ - Success: WP-Standard-Eval - """ - - When I run `wp --require=override/override.php cli version` - Then STDOUT should contain: - """ - WP-Override-CLI - """ - - When I run `wp --require=override/override.php eval "\WP_CLI::Success( 'WP-Standard-Eval' );"` - Then STDOUT should contain: - """ - Success: WP-Override-Eval - """ + When I run `wp --require=cli-override-command/cli.php cli version` + Then STDOUT should contain: + """ + WP-Override-CLI + """ - Scenario: Override command through package manager + Scenario: Override command bundled with freshly built PHAR - Given a WP installation - And a override/override.php file: + Given an empty directory + And a new Phar with the same version + And a cli-override-command/cli.php file: """ 'before_wp_load' ) ); - // Override bundled command. - WP_CLI::add_hook( - 'after_add_command:eval', - static function () { - static $added = false; - if ( ! $added ) { - $added = true; - WP_CLI::add_command( 'eval', 'Custom_Eval_Command', array( 'when' => 'before_wp_load' ) ); - } - } - ); + WP_CLI::add_command( 'cli', 'CLI_Command', array( 'when' => 'before_wp_load' ) ); """ - And a override/src/Custom_CLI_Command.php file: + And a cli-override-command/src/CLI_Command.php file: """ &1` - Given a WP installation + When I run `{PHAR_PATH} cli version` + Then STDOUT should contain: + """ + WP-CLI + """ - # Expect a warning from WP core for PHP 8+. - When I try `wp --exec="define( 'WP_ADMIN', true );" eval "echo WP_ADMIN;"` - Then STDOUT should contain: - """ - 1 - """ + When I run `{PHAR_PATH} --require=cli-override-command/cli.php cli version` + Then STDOUT should contain: + """ + WP-Override-CLI + """ Scenario: Composer stack with both WordPress and wp-cli as dependencies (command line) Given a WP installation with Composer And a dependency on current wp-cli - # Redirect STDERR to STDOUT as Composer produces non-error output on STDERR - And I run `composer require wp-cli/entity-command --with-all-dependencies --no-interaction 2>&1` - When I run `vendor/bin/wp option get blogname` Then STDOUT should contain: """ WP CLI Site with both WordPress and wp-cli as Composer dependencies """ - @broken + @require-php-5.4 Scenario: Composer stack with both WordPress and wp-cli as dependencies (web) Given a WP installation with Composer And a dependency on current wp-cli - And a PHP built-in web server to serve 'WordPress' + And a PHP built-in web server to serve 'wordpress' Then the HTTP status code should be 200 Scenario: Composer stack with both WordPress and wp-cli as dependencies and a custom vendor directory Given a WP installation with Composer and a custom vendor directory 'vendor-custom' And a dependency on current wp-cli - # Redirect STDERR to STDOUT as Composer produces non-error output on STDERR - And I run `composer require wp-cli/entity-command --with-all-dependencies --no-interaction 2>&1` - + Then the vendor-custom/autoload_commands.php file should exist + Then the vendor-custom/autoload_framework.php file should exist When I run `vendor-custom/bin/wp option get blogname` Then STDOUT should contain: """ @@ -383,7 +244,7 @@ Feature: Bootstrap WP-CLI define( 'WP_CLI_TEST_CONSTANT', getenv( 'WP_CLI_TEST_ENV_VAR' ) ); """ - When I run `wp config create --skip-check {CORE_CONFIG_SETTINGS}` + When I run `wp config create {CORE_CONFIG_SETTINGS}` Then STDOUT should contain: """ Success: @@ -397,19 +258,19 @@ Feature: Bootstrap WP-CLI """ And the return code should be 0 - When I run `wp eval "echo constant( 'WP_CLI_TEST_CONSTANT' );"` + When I run `wp eval 'echo constant( "WP_CLI_TEST_CONSTANT" );'` Then STDOUT should be: """ foo """ + @require-wp-3.9 Scenario: Run cache flush on ms_site_not_found Given a WP multisite installation And a wp-cli.yml file: """ url: invalid.com """ - And I run `wp package install wp-cli/cache-command` When I try `wp cache add foo bar` Then STDERR should contain: @@ -418,23 +279,20 @@ Feature: Bootstrap WP-CLI """ And the return code should be 1 - When I try `wp cache flush --url=invalid.com` + When I run `wp cache flush --url=invalid.com` Then STDOUT should contain: """ - Success: The cache was flushed. + Success: """ And the return code should be 0 - # `wp search-replace` does not yet support SQLite - # See https://github.com/wp-cli/search-replace-command/issues/190 - @require-mysql + @require-wp-4.0 Scenario: Run search-replace on ms_site_not_found Given a WP multisite installation And a wp-cli.yml file: """ url: invalid.com """ - And I run `wp package install wp-cli/search-replace-command` When I try `wp search-replace foo bar` Then STDERR should contain: @@ -498,52 +356,3 @@ Feature: Bootstrap WP-CLI Success: """ And the return code should be 0 - - @skip-windows - Scenario: Allow disabling ini_set() - Given an empty directory - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=ini_set} cli info` - Then the return code should be 0 - - # TODO: Make test work for Windows. - @skip-windows - Scenario: Test early root detection - - Given an empty directory - And a include.php file: - """ - - """ - - And I try `WP_CLI_EARLY_REQUIRE=include.php wp cli version --debug` - - Then STDERR should contain: - """ - WP_CLI\Bootstrap\CheckRoot - """ - - And STDERR should not contain: - """ - WP_CLI\Bootstrap\IncludeRequestsAutoloader - """ - - And STDERR should contain: - """ - YIKES! - """ - - Scenario: Package autoloader has priority over fallback autoloader - Given an empty directory - - # Verify both autoloaders are loaded in debug output, and that the fallback - # autoloader is initialized before the package autoloader so that locally - # installed packages override phar-bundled versions. - When I try `wp cli version --debug` - Then STDERR should match /WP_CLI\\Bootstrap\\IncludeFallbackAutoloader[\s\S]*WP_CLI\\Bootstrap\\IncludePackageAutoloader/ diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php new file mode 100644 index 000000000..6f88ed1bb --- /dev/null +++ b/features/bootstrap/FeatureContext.php @@ -0,0 +1,942 @@ +autoload->files ) ) { + $contents = 'require:' . PHP_EOL; + foreach( $composer->autoload->files as $file ) { + $contents .= ' - ' . dirname( dirname( dirname( __FILE__ ) ) ) . '/' . $file . PHP_EOL; + } + @mkdir( sys_get_temp_dir() . '/wp-cli-package-test/' ); + $project_config = sys_get_temp_dir() . '/wp-cli-package-test/config.yml'; + file_put_contents( $project_config, $contents ); + putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); + } + } +// Inside WP-CLI +} else { + require_once __DIR__ . '/../../php/utils.php'; + require_once __DIR__ . '/../../php/WP_CLI/Process.php'; + require_once __DIR__ . '/../../php/WP_CLI/ProcessRun.php'; + if ( file_exists( __DIR__ . '/../../vendor/autoload.php' ) ) { + require_once __DIR__ . '/../../vendor/autoload.php'; + } else if ( file_exists( __DIR__ . '/../../../../autoload.php' ) ) { + require_once __DIR__ . '/../../../../autoload.php'; + } +} + +/** + * Features context. + */ +class FeatureContext extends BehatContext implements ClosuredContextInterface { + + /** + * The current working directory for scenarios that have a "Given a WP installation" or "Given an empty directory" step. Variable RUN_DIR. Lives until the end of the scenario. + */ + private static $run_dir; + + /** + * Where WordPress core is downloaded to for caching, and which is copied to RUN_DIR during a "Given a WP installation" step. Lives until manually deleted. + */ + private static $cache_dir; + + /** + * The directory that holds the install cache, and which is copied to RUN_DIR during a "Given a WP installation" step. Recreated on each suite run. + */ + private static $install_cache_dir; + + /** + * The directory that the WP-CLI cache (WP_CLI_CACHE_DIR, normally "$HOME/.wp-cli/cache") is set to on a "Given an empty cache" step. + * Variable SUITE_CACHE_DIR. Lives until the end of the scenario (or until another "Given an empty cache" step within the scenario). + */ + private static $suite_cache_dir; + + /** + * Where the current WP-CLI source repository is copied to for Composer-based tests with a "Given a dependency on current wp-cli" step. + * Variable COMPOSER_LOCAL_REPOSITORY. Lives until the end of the suite. + */ + private static $composer_local_repository; + + /** + * The test database settings. All but `dbname` can be set via environment variables. The database is dropped at the start of each scenario and created on a "Given a WP installation" step. + */ + private static $db_settings = array( + 'dbname' => 'wp_cli_test', + 'dbuser' => 'wp_cli_test', + 'dbpass' => 'password1', + 'dbhost' => '127.0.0.1', + ); + + /** + * Array of background process ids started by the current scenario. Used to terminate them at the end of the scenario. + */ + private $running_procs = array(); + + /** + * Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest. + * Some are step-dependent: RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. One is set on use: INVOKE_WP_CLI_WITH_PHP_ARGS-args. + * Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario. + */ + public $variables = array(); + + /** + * The current feature file and scenario line number as '.'. Used in RUN_DIR and SUITE_CACHE_DIR directory names. Set at the start of each scenario. + */ + private static $temp_dir_infix; + + /** + * Settings and variables for WP_CLI_TEST_LOG_RUN_TIMES run time logging. + */ + private static $log_run_times; // Whether to log run times - WP_CLI_TEST_LOG_RUN_TIMES env var. Set on `@BeforeScenario'. + private static $suite_start_time; // When the suite started, set on `@BeforeScenario'. + private static $output_to; // Where to output log - stdout|error_log. Set on `@BeforeSuite`. + private static $num_top_processes; // Number of processes/methods to output by longest run times. Set on `@BeforeSuite`. + private static $num_top_scenarios; // Number of scenarios to output by longest run times. Set on `@BeforeSuite`. + + private static $scenario_run_times = array(); // Scenario run times (top `self::$num_top_scenarios` only). + private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`. + private static $proc_method_run_times = array(); // Array of run time info for proc methods, keyed by method name and arg, each a 2-element array containing run time and run count. + + /** + * Get the environment variables required for launched `wp` processes + */ + private static function get_process_env_variables() { + // Ensure we're using the expected `wp` binary + $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' ); + $vendor_dir = realpath( __DIR__ . '/../../vendor/bin' ); + $path_separator = Utils\is_windows() ? ';' : ':'; + $env = array( + 'PATH' => $bin_dir . $path_separator . $vendor_dir . $path_separator . getenv( 'PATH' ), + 'BEHAT_RUN' => 1, + 'HOME' => sys_get_temp_dir() . '/wp-cli-home', + ); + if ( $config_path = getenv( 'WP_CLI_CONFIG_PATH' ) ) { + $env['WP_CLI_CONFIG_PATH'] = $config_path; + } + if ( $term = getenv( 'TERM' ) ) { + $env['TERM'] = $term; + } + if ( $php_args = getenv( 'WP_CLI_PHP_ARGS' ) ) { + $env['WP_CLI_PHP_ARGS'] = $php_args; + } + if ( $php_used = getenv( 'WP_CLI_PHP_USED' ) ) { + $env['WP_CLI_PHP_USED'] = $php_used; + } + if ( $php = getenv( 'WP_CLI_PHP' ) ) { + $env['WP_CLI_PHP'] = $php; + } + if ( $travis_build_dir = getenv( 'TRAVIS_BUILD_DIR' ) ) { + $env['TRAVIS_BUILD_DIR'] = $travis_build_dir; + } + if ( $github_token = getenv( 'GITHUB_TOKEN' ) ) { + $env['GITHUB_TOKEN'] = $github_token; + } + return $env; + } + + /** + * We cache the results of `wp core download` to improve test performance. + * Ideally, we'd cache at the HTTP layer for more reliable tests. + */ + private static function cache_wp_files() { + $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; + self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; + + if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) + return; + + $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir ); + if ( $wp_version ) { + $cmd .= Utils\esc_cmd( ' --version=%s', $wp_version ); + } + Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); + } + + /** + * @BeforeSuite + */ + public static function prepare( SuiteEvent $event ) { + // Test performance statistics - useful for detecting slow tests. + if ( self::$log_run_times = getenv( 'WP_CLI_TEST_LOG_RUN_TIMES' ) ) { + self::log_run_times_before_suite( $event ); + } + + $result = Process::create( 'wp cli info', null, self::get_process_env_variables() )->run_check(); + echo PHP_EOL; + echo $result->stdout; + echo PHP_EOL; + self::cache_wp_files(); + $result = Process::create( Utils\esc_cmd( 'wp core version --path=%s', self::$cache_dir ) , null, self::get_process_env_variables() )->run_check(); + echo 'WordPress ' . $result->stdout; + echo PHP_EOL; + + // Remove install cache if any (not setting the static var). + $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; + $install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; + if ( file_exists( $install_cache_dir ) ) { + self::remove_dir( $install_cache_dir ); + } + } + + /** + * @AfterSuite + */ + public static function afterSuite( SuiteEvent $event ) { + if ( self::$composer_local_repository ) { + self::remove_dir( self::$composer_local_repository ); + self::$composer_local_repository = null; + } + + if ( self::$log_run_times ) { + self::log_run_times_after_suite( $event ); + } + } + + /** + * @BeforeScenario + */ + public function beforeScenario( $event ) { + if ( self::$log_run_times ) { + self::log_run_times_before_scenario( $event ); + } + + $this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' ); + + // Used in the names of the RUN_DIR and SUITE_CACHE_DIR directories. + self::$temp_dir_infix = null; + if ( $file = self::get_event_file( $event, $line ) ) { + self::$temp_dir_infix = basename( $file ) . '.' . $line; + } + } + + /** + * @AfterScenario + */ + public function afterScenario( $event ) { + + if ( self::$run_dir ) { + // remove altered WP install, unless there's an error + if ( $event->getResult() < 4 ) { + self::remove_dir( self::$run_dir ); + } + self::$run_dir = null; + } + + // Remove WP-CLI package directory if any. Set to `wp package path` by package-command and scaffold-package-command features, and by cli-info.feature. + if ( isset( $this->variables['PACKAGE_PATH'] ) ) { + self::remove_dir( $this->variables['PACKAGE_PATH'] ); + } + + // Remove SUITE_CACHE_DIR if any. + if ( self::$suite_cache_dir ) { + self::remove_dir( self::$suite_cache_dir ); + self::$suite_cache_dir = null; + } + + // Remove any background processes. + foreach ( $this->running_procs as $proc ) { + $status = proc_get_status( $proc ); + self::terminate_proc( $status['pid'] ); + } + + if ( self::$log_run_times ) { + self::log_run_times_after_scenario( $event ); + } + } + + /** + * Terminate a process and any of its children. + */ + private static function terminate_proc( $master_pid ) { + + $output = `ps -o ppid,pid,command | grep $master_pid`; + + foreach ( explode( PHP_EOL, $output ) as $line ) { + if ( preg_match( '/^\s*(\d+)\s+(\d+)/', $line, $matches ) ) { + $parent = $matches[1]; + $child = $matches[2]; + + if ( $parent == $master_pid ) { + self::terminate_proc( $child ); + } + } + } + + if ( ! posix_kill( (int) $master_pid, 9 ) ) { + $errno = posix_get_last_error(); + // Ignore "No such process" error as that's what we want. + if ( 3 /*ESRCH*/ !== $errno ) { + throw new RuntimeException( posix_strerror( $errno ) ); + } + } + } + + /** + * Create a temporary WP_CLI_CACHE_DIR. Exposed as SUITE_CACHE_DIR in "Given an empty cache" step. + */ + public static function create_cache_dir() { + if ( self::$suite_cache_dir ) { + self::remove_dir( self::$suite_cache_dir ); + } + self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-suite-cache-' . self::$temp_dir_infix . '-', TRUE ); + mkdir( self::$suite_cache_dir ); + return self::$suite_cache_dir; + } + + /** + * Initializes context. + * Every scenario gets its own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct( array $parameters ) { + if ( getenv( 'WP_CLI_TEST_DBUSER' ) ) { + self::$db_settings['dbuser'] = getenv( 'WP_CLI_TEST_DBUSER' ); + } + + if ( false !== getenv( 'WP_CLI_TEST_DBPASS' ) ) { + self::$db_settings['dbpass'] = getenv( 'WP_CLI_TEST_DBPASS' ); + } + + if ( getenv( 'WP_CLI_TEST_DBHOST' ) ) { + self::$db_settings['dbhost'] = getenv( 'WP_CLI_TEST_DBHOST' ); + } + + $this->drop_db(); + $this->set_cache_dir(); + $this->variables['CORE_CONFIG_SETTINGS'] = Utils\assoc_args_to_str( self::$db_settings ); + } + + public function getStepDefinitionResources() { + return glob( __DIR__ . '/../steps/*.php' ); + } + + public function getHookDefinitionResources() { + return array(); + } + + /** + * Replace standard {VARIABLE_NAME} variables and the special {INVOKE_WP_CLI_WITH_PHP_ARGS-args} and {WP_VERSION-version-latest} variables. + * Note that standard variable names can only contain uppercase letters, digits and underscores and cannot begin with a digit. + */ + public function replace_variables( $str ) { + if ( false !== strpos( $str, '{INVOKE_WP_CLI_WITH_PHP_ARGS-' ) ) { + $str = $this->replace_invoke_wp_cli_with_php_args( $str ); + } + $str = preg_replace_callback( '/\{([A-Z_][A-Z_0-9]*)\}/', array( $this, 'replace_var' ), $str ); + if ( false !== strpos( $str, '{WP_VERSION-' ) ) { + $str = $this->replace_wp_versions( $str ); + } + return $str; + } + + /** + * Substitute {INVOKE_WP_CLI_WITH_PHP_ARGS-args} variables. + */ + private function replace_invoke_wp_cli_with_php_args( $str ) { + static $phar_path = null, $shell_path = null; + + if ( null === $phar_path ) { + $phar_path = false; + $phar_begin = '#!/usr/bin/env php'; + $phar_begin_len = strlen( $phar_begin ); + if ( ( $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ) && file_exists( $bin_dir . '/wp' ) && $phar_begin === file_get_contents( $bin_dir . '/wp', false, null, 0, $phar_begin_len ) ) { + $phar_path = $bin_dir . '/wp'; + } else { + $src_dir = dirname( dirname( __DIR__ ) ); + $bin_path = $src_dir . '/bin/wp'; + $vendor_bin_path = $src_dir . '/vendor/bin/wp'; + if ( file_exists( $bin_path ) && is_executable( $bin_path ) ) { + $shell_path = $bin_path; + } elseif ( file_exists( $vendor_bin_path ) && is_executable( $vendor_bin_path ) ) { + $shell_path = $vendor_bin_path; + } else { + $shell_path = 'wp'; + } + } + } + + $str = preg_replace_callback( '/{INVOKE_WP_CLI_WITH_PHP_ARGS-([^}]*)}/', function ( $matches ) use ( $phar_path, $shell_path ) { + return $phar_path ? "php {$matches[1]} {$phar_path}" : ( 'WP_CLI_PHP_ARGS=' . escapeshellarg( $matches[1] ) . ' ' . $shell_path ); + }, $str ); + + return $str; + } + + /** + * Replace variables callback. + */ + private function replace_var( $matches ) { + $cmd = $matches[0]; + + foreach ( array_slice( $matches, 1 ) as $key ) { + $cmd = str_replace( '{' . $key . '}', $this->variables[ $key ], $cmd ); + } + + return $cmd; + } + + /** + * Substitute {WP_VERSION-version-latest} variables. + */ + private function replace_wp_versions( $str ) { + static $wp_versions = null; + if ( null === $wp_versions ) { + $wp_versions = array(); + + $response = Requests::get( 'https://api.wordpress.org/core/version-check/1.7/', null, array( 'timeout' => 30 ) ); + if ( 200 === $response->status_code && ( $body = json_decode( $response->body ) ) && is_object( $body ) && isset( $body->offers ) && is_array( $body->offers ) ) { + // Latest version alias. + $wp_versions["{WP_VERSION-latest}"] = count( $body->offers ) ? $body->offers[0]->version : ''; + foreach ( $body->offers as $offer ) { + $sub_ver = preg_replace( '/(^[0-9]+\.[0-9]+)\.[0-9]+$/', '$1', $offer->version ); + $sub_ver_key = "{WP_VERSION-{$sub_ver}-latest}"; + + $main_ver = preg_replace( '/(^[0-9]+)\.[0-9]+$/', '$1', $sub_ver ); + $main_ver_key = "{WP_VERSION-{$main_ver}-latest}"; + + if ( ! isset( $wp_versions[ $main_ver_key ] ) ) { + $wp_versions[ $main_ver_key ] = $offer->version; + } + if ( ! isset( $wp_versions[ $sub_ver_key ] ) ) { + $wp_versions[ $sub_ver_key ] = $offer->version; + } + } + } + } + return strtr( $str, $wp_versions ); + } + + /** + * Get the file and line number for the current behat event. + */ + private static function get_event_file( $event, &$line ) { + if ( method_exists( $event, 'getScenario' ) ) { + $scenario_feature = $event->getScenario(); + } elseif ( method_exists( $event, 'getFeature' ) ) { + $scenario_feature = $event->getFeature(); + } elseif ( method_exists( $event, 'getOutline' ) ) { + $scenario_feature = $event->getOutline(); + } else { + return null; + } + $line = $scenario_feature->getLine(); + return $scenario_feature->getFile(); + } + + /** + * Create the RUN_DIR directory, unless already set for this scenario. + */ + public function create_run_dir() { + if ( !isset( $this->variables['RUN_DIR'] ) ) { + self::$run_dir = $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', TRUE ); + mkdir( $this->variables['RUN_DIR'] ); + } + } + + public function build_phar( $version = 'same' ) { + $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( "wp-cli-build-", TRUE ) . '.phar'; + + // Test running against a package installed as a WP-CLI dependency + // WP-CLI installed as a project dependency + $make_phar_path = __DIR__ . '/../../../../../utils/make-phar.php'; + if ( ! file_exists( $make_phar_path ) ) { + // Test running against WP-CLI proper + $make_phar_path = __DIR__ . '/../../utils/make-phar.php'; + if ( ! file_exists( $make_phar_path ) ) { + // WP-CLI as a dependency of this project + $make_phar_path = __DIR__ . '/../../vendor/wp-cli/wp-cli/utils/make-phar.php'; + } + } + + $this->proc( Utils\esc_cmd( + 'php -dphar.readonly=0 %1$s %2$s --version=%3$s && chmod +x %2$s', + $make_phar_path, + $this->variables['PHAR_PATH'], + $version + ) )->run_check(); + } + + public function download_phar( $version = 'same' ) { + if ( 'same' === $version ) { + $version = WP_CLI_VERSION; + } + + $download_url = sprintf( + 'https://github.com/wp-cli/wp-cli/releases/download/v%1$s/wp-cli-%1$s.phar', + $version + ); + + $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' + . uniqid( 'wp-cli-download-', true ) + . '.phar'; + + Process::create( Utils\esc_cmd( + 'curl -sSfL %1$s > %2$s && chmod +x %2$s', + $download_url, + $this->variables['PHAR_PATH'] + ) )->run_check(); + } + + /** + * CACHE_DIR is a cache for downloaded test data such as images. Lives until manually deleted. + */ + private function set_cache_dir() { + $path = sys_get_temp_dir() . '/wp-cli-test-cache'; + if ( ! file_exists( $path ) ) { + mkdir( $path ); + } + $this->variables['CACHE_DIR'] = $path; + } + + /** + * Run a MySQL command with `$db_settings`. + * + * @param string $sql_cmd Command to run. + * @param array $assoc_args Optional. Associative array of options. Default empty. + * @param bool $add_database Optional. Whether to add dbname to the $sql_cmd. Default false. + */ + private static function run_sql( $sql_cmd, $assoc_args = array(), $add_database = false ) { + $default_assoc_args = array( + 'host' => self::$db_settings['dbhost'], + 'user' => self::$db_settings['dbuser'], + 'pass' => self::$db_settings['dbpass'], + ); + if ( $add_database ) { + $sql_cmd .= ' ' . escapeshellarg( self::$db_settings['dbname'] ); + } + $start_time = microtime( true ); + Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ) ); + if ( self::$log_run_times ) { + self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); + } + } + + public function create_db() { + $dbname = self::$db_settings['dbname']; + self::run_sql( 'mysql --no-defaults', array( 'execute' => "CREATE DATABASE IF NOT EXISTS $dbname" ) ); + } + + public function drop_db() { + $dbname = self::$db_settings['dbname']; + self::run_sql( 'mysql --no-defaults', array( 'execute' => "DROP DATABASE IF EXISTS $dbname" ) ); + } + + public function proc( $command, $assoc_args = array(), $path = '' ) { + if ( !empty( $assoc_args ) ) + $command .= Utils\assoc_args_to_str( $assoc_args ); + + $env = self::get_process_env_variables(); + if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { + $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; + } + + if ( isset( $this->variables['RUN_DIR'] ) ) { + $cwd = "{$this->variables['RUN_DIR']}/{$path}"; + } else { + $cwd = null; + } + + return Process::create( $command, $cwd, $env ); + } + + /** + * Start a background process. Will automatically be closed when the tests finish. + */ + public function background_proc( $cmd ) { + $descriptors = array( + 0 => STDIN, + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); + + $proc = proc_open( $cmd, $descriptors, $pipes, $this->variables['RUN_DIR'], self::get_process_env_variables() ); + + sleep(1); + + $status = proc_get_status( $proc ); + + if ( !$status['running'] ) { + $stderr = is_resource( $pipes[2] ) ? ( ': ' . stream_get_contents( $pipes[2] ) ) : ''; + throw new RuntimeException( sprintf( "Failed to start background process '%s'%s.", $cmd, $stderr ) ); + } else { + $this->running_procs[] = $proc; + } + } + + public function move_files( $src, $dest ) { + rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" ); + } + + /** + * Remove a directory (recursive). + */ + public static function remove_dir( $dir ) { + Process::create( Utils\esc_cmd( 'rm -rf %s', $dir ) )->run_check(); + } + + /** + * Copy a directory (recursive). Destination directory must exist. + */ + public static function copy_dir( $src_dir, $dest_dir ) { + Process::create( Utils\esc_cmd( "cp -r %s/* %s", $src_dir, $dest_dir ) )->run_check(); + } + + public function add_line_to_wp_config( &$wp_config_code, $line ) { + $token = "/* That's all, stop editing!"; + + $wp_config_code = str_replace( $token, "$line\n\n$token", $wp_config_code ); + } + + public function download_wp( $subdir = '' ) { + $dest_dir = $this->variables['RUN_DIR'] . "/$subdir"; + + if ( $subdir ) { + mkdir( $dest_dir ); + } + + self::copy_dir( self::$cache_dir, $dest_dir ); + + // disable emailing + mkdir( $dest_dir . '/wp-content/mu-plugins' ); + copy( __DIR__ . '/../extra/no-mail.php', $dest_dir . '/wp-content/mu-plugins/no-mail.php' ); + } + + public function create_config( $subdir = '', $extra_php = false ) { + $params = self::$db_settings; + + // Replaces all characters that are not alphanumeric or an underscore into an underscore. + $params['dbprefix'] = $subdir ? preg_replace( '#[^a-zA-Z\_0-9]#', '_', $subdir ) : 'wp_'; + + $params['skip-salts'] = true; + + if( false !== $extra_php ) { + $params['extra-php'] = $extra_php; + } + + $config_cache_path = ''; + if ( self::$install_cache_dir ) { + $config_cache_path = self::$install_cache_dir . '/config_' . md5( implode( ':', $params ) . ':subdir=' . $subdir ); + $run_dir = '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR']; + } + + if ( $config_cache_path && file_exists( $config_cache_path ) ) { + copy( $config_cache_path, $run_dir . '/wp-config.php' ); + } else { + $this->proc( 'wp config create', $params, $subdir )->run_check(); + if ( $config_cache_path && file_exists( $run_dir . '/wp-config.php' ) ) { + copy( $run_dir . '/wp-config.php', $config_cache_path ); + } + } + } + + public function install_wp( $subdir = '' ) { + $wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; + self::$install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; + if ( ! file_exists( self::$install_cache_dir ) ) { + mkdir( self::$install_cache_dir ); + } + + $subdir = $this->replace_variables( $subdir ); + + $this->create_db(); + $this->create_run_dir(); + $this->download_wp( $subdir ); + $this->create_config( $subdir ); + + $install_args = array( + 'url' => 'http://example.com', + 'title' => 'WP CLI Site', + 'admin_user' => 'admin', + 'admin_email' => 'admin@example.com', + 'admin_password' => 'password1', + 'skip-email' => true, + ); + + $install_cache_path = ''; + if ( self::$install_cache_dir ) { + $install_cache_path = self::$install_cache_dir . '/install_' . md5( implode( ':', $install_args ) . ':subdir=' . $subdir ); + $run_dir = '' !== $subdir ? ( $this->variables['RUN_DIR'] . "/$subdir" ) : $this->variables['RUN_DIR']; + } + + if ( $install_cache_path && file_exists( $install_cache_path ) ) { + self::copy_dir( $install_cache_path, $run_dir ); + self::run_sql( 'mysql --no-defaults', array( 'execute' => "source {$install_cache_path}.sql" ), true /*add_database*/ ); + } else { + $this->proc( 'wp core install', $install_args, $subdir )->run_check(); + if ( $install_cache_path ) { + mkdir( $install_cache_path ); + self::dir_diff_copy( $run_dir, self::$cache_dir, $install_cache_path ); + self::run_sql( 'mysqldump --no-defaults', array( 'result-file' => "{$install_cache_path}.sql" ), true /*add_database*/ ); + } + } + } + + public function install_wp_with_composer( $vendor_directory = 'vendor' ) { + $this->create_run_dir(); + $this->create_db(); + + $yml_path = $this->variables['RUN_DIR'] . "/wp-cli.yml"; + file_put_contents( $yml_path, 'path: wordpress' ); + + $this->composer_command( 'init --name="wp-cli/composer-test" --type="project" --no-interaction' ); + $this->composer_command( 'config vendor-dir ' . $vendor_directory ); + $this->composer_command( 'require johnpbloch/wordpress --optimize-autoloader --no-interaction' ); + + $config_extra_php = "require_once dirname(__DIR__) . '/" . $vendor_directory . "/autoload.php';"; + $this->create_config( 'wordpress', $config_extra_php ); + + $install_args = array( + 'url' => 'http://localhost:8080', + 'title' => 'WP CLI Site with both WordPress and wp-cli as Composer dependencies', + 'admin_user' => 'admin', + 'admin_email' => 'admin@example.com', + 'admin_password' => 'password1', + 'skip-email' => true, + ); + + $this->proc( 'wp core install', $install_args )->run_check(); + } + + public function composer_add_wp_cli_local_repository() { + if ( ! self::$composer_local_repository ) { + self::$composer_local_repository = sys_get_temp_dir() . '/' . uniqid( "wp-cli-composer-local-", TRUE ); + mkdir( self::$composer_local_repository ); + + $env = self::get_process_env_variables(); + $src = isset( $env['TRAVIS_BUILD_DIR'] ) ? $env['TRAVIS_BUILD_DIR'] : realpath( __DIR__ . '/../../' ); + + self::copy_dir( $src, self::$composer_local_repository . '/' ); + self::remove_dir( self::$composer_local_repository . '/.git' ); + self::remove_dir( self::$composer_local_repository . '/vendor' ); + } + $dest = self::$composer_local_repository . '/'; + $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false}}'" ); + $this->variables['COMPOSER_LOCAL_REPOSITORY'] = self::$composer_local_repository; + } + + public function composer_require_current_wp_cli() { + $this->composer_add_wp_cli_local_repository(); + $this->composer_command( 'require wp-cli/wp-cli:dev-master --optimize-autoloader --no-interaction' ); + } + + public function start_php_server( $subdir = '' ) { + $dir = $this->variables['RUN_DIR'] . '/'; + if ( $subdir ) { + $dir .= trim( $subdir, '/' ) . '/'; + } + $cmd = Utils\esc_cmd( '%s -S %s -t %s -c %s %s', + Utils\get_php_binary(), + 'localhost:8080', + $dir, + get_cfg_var( 'cfg_file_path' ), + $this->variables['RUN_DIR'] . '/vendor/wp-cli/server-command/router.php' + ); + $this->background_proc( $cmd ); + } + + private function composer_command($cmd) { + if ( !isset( $this->variables['COMPOSER_PATH'] ) ) { + $this->variables['COMPOSER_PATH'] = exec('which composer'); + } + $this->proc( $this->variables['COMPOSER_PATH'] . ' ' . $cmd )->run_check(); + } + + /** + * Initialize run time logging. + */ + private static function log_run_times_before_suite( $event ) { + self::$suite_start_time = microtime( true ); + + Process::$log_run_times = true; + + $travis = getenv( 'TRAVIS' ); + + // Default output settings. + self::$output_to = 'stdout'; + self::$num_top_processes = $travis ? 10 : 40; + self::$num_top_scenarios = $travis ? 10 : 20; + + // Allow setting of above with "WP_CLI_TEST_LOG_RUN_TIMES=[,][,]" formatted env var. + if ( preg_match( '/^(stdout|error_log)?(,[0-9]+)?(,[0-9]+)?$/i', self::$log_run_times, $matches ) ) { + if ( isset( $matches[1] ) ) { + self::$output_to = strtolower( $matches[1] ); + } + if ( isset( $matches[2] ) ) { + self::$num_top_processes = max( (int) substr( $matches[2], 1 ), 1 ); + } + if ( isset( $matches[3] ) ) { + self::$num_top_scenarios = max( (int) substr( $matches[3], 1 ), 1 ); + } + } + } + + /** + * Record the start time of the scenario into the `$scenario_run_times` array. + */ + private static function log_run_times_before_scenario( $event ) { + if ( $scenario_key = self::get_scenario_key( $event ) ) { + self::$scenario_run_times[ $scenario_key ] = -microtime( true ); + } + } + + /** + * Save the run time of the scenario into the `$scenario_run_times` array. Only the top `self::$num_top_scenarios` are kept. + */ + private static function log_run_times_after_scenario( $event ) { + if ( $scenario_key = self::get_scenario_key( $event ) ) { + self::$scenario_run_times[ $scenario_key ] += microtime( true ); + self::$scenario_count++; + if ( count( self::$scenario_run_times ) > self::$num_top_scenarios ) { + arsort( self::$scenario_run_times ); + array_pop( self::$scenario_run_times ); + } + } + } + + /** + * Copy files in updated directory that are not in source directory to copy directory. ("Incremental backup".) + * Note: does not deal with changed files (ie does not compare file contents for changes), for speed reasons. + * + * @param string $upd_dir The directory to search looking for files/directories not in `$src_dir`. + * @param string $src_dir The directory to be compared to `$upd_dir`. + * @param string $cop_dir Where to copy any files/directories in `$upd_dir` but not in `$src_dir` to. + */ + private static function dir_diff_copy( $upd_dir, $src_dir, $cop_dir ) { + if ( false === ( $files = scandir( $upd_dir ) ) ) { + $error = error_get_last(); + throw new \RuntimeException( sprintf( "Failed to open updated directory '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_dir, $error['message'] ) ); + } + foreach ( array_diff( $files, array( '.', '..' ) ) as $file ) { + $upd_file = $upd_dir . '/' . $file; + $src_file = $src_dir . '/' . $file; + $cop_file = $cop_dir . '/' . $file; + if ( ! file_exists( $src_file ) ) { + if ( is_dir( $upd_file ) ) { + if ( ! file_exists( $cop_file ) && ! mkdir( $cop_file, 0777, true /*recursive*/ ) ) { + $error = error_get_last(); + throw new \RuntimeException( sprintf( "Failed to create copy directory '%s': %s. " . __FILE__ . ':' . __LINE__, $cop_file, $error['message'] ) ); + } + self::copy_dir( $upd_file, $cop_file ); + } else { + if ( ! copy( $upd_file, $cop_file ) ) { + $error = error_get_last(); + throw new \RuntimeException( sprintf( "Failed to copy '%s' to '%s': %s. " . __FILE__ . ':' . __LINE__, $upd_file, $cop_file, $error['message'] ) ); + } + } + } elseif ( is_dir( $upd_file ) ) { + self::dir_diff_copy( $upd_file, $src_file, $cop_file ); + } + } + } + + /** + * Get the scenario key used for `$scenario_run_times` array. + * Format " :", eg "core-command core-update.feature:221". + */ + private static function get_scenario_key( $event ) { + $scenario_key = ''; + if ( $file = self::get_event_file( $event, $line ) ) { + $scenario_grandparent = Utils\basename( dirname( dirname( $file ) ) ); + $scenario_key = $scenario_grandparent . ' ' . Utils\basename( $file ) . ':' . $line; + } + return $scenario_key; + } + + /** + * Print out stats on the run times of processes and scenarios. + */ + private static function log_run_times_after_suite( $event ) { + + $suite = ''; + if ( self::$scenario_run_times ) { + // Grandparent directory is first part of key. + $keys = array_keys( self::$scenario_run_times ); + $suite = substr( $keys[0], 0, strpos( $keys[0], ' ' ) ); + } + + $run_from = Utils\basename( dirname( dirname( __DIR__ ) ) ); + + // Format same as Behat, if have minutes. + $fmt = function ( $time ) { + $mins = floor( $time / 60 ); + return round( $time, 3 ) . ( $mins ? ( ' (' . $mins . 'm' . round( $time - ( $mins * 60 ), 3 ) . 's)' ) : '' ); + }; + + $time = microtime( true ) - self::$suite_start_time; + + $log = PHP_EOL . str_repeat( '(', 80 ) . PHP_EOL; + + // Process and proc method run times. + $run_times = array_merge( Process::$run_times, self::$proc_method_run_times ); + + list( $ptime, $calls ) = array_reduce( $run_times, function ( $carry, $item ) { + return array( $carry[0] + $item[0], $carry[1] + $item[1] ); + }, array( 0, 0 ) ); + + $overhead = $time - $ptime; + $pct = round( ( $overhead / $time ) * 100 ); + $unique = count( $run_times ); + + $log .= sprintf( + PHP_EOL . "Total process run time %s (tests %s, overhead %.3f %d%%), calls %d (%d unique) for '%s' run from '%s'" . PHP_EOL, + $fmt( $ptime ), $fmt( $time ), $overhead, $pct, $calls, $unique, $suite, $run_from + ); + + uasort( $run_times, function ( $a, $b ) { + return $a[0] === $b[0] ? 0 : ( $a[0] < $b[0] ? 1 : -1 ); // Reverse sort. + } ); + + $tops = array_slice( $run_times, 0, self::$num_top_processes, true ); + + $log .= PHP_EOL . "Top " . self::$num_top_processes . " process run times for '$suite'"; + $log .= PHP_EOL . implode( PHP_EOL, array_map( function ( $k, $v, $i ) { + return sprintf( ' %3d. %7.3f %3d %s', $i + 1, round( $v[0], 3 ), $v[1], $k ); + }, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; + + // Scenario run times. + arsort( self::$scenario_run_times ); + + $tops = array_slice( self::$scenario_run_times, 0, self::$num_top_scenarios, true ); + + $log .= PHP_EOL . "Top " . self::$num_top_scenarios . " (of " . self::$scenario_count . ") scenario run times for '$suite'"; + $log .= PHP_EOL . implode( PHP_EOL, array_map( function ( $k, $v, $i ) { + return sprintf( ' %3d. %7.3f %s', $i + 1, round( $v, 3 ), substr( $k, strpos( $k, ' ' ) + 1 ) ); + }, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; + + $log .= PHP_EOL . str_repeat( ')', 80 ); + + if ( 'error_log' === self::$output_to ) { + error_log( $log ); + } else { + echo PHP_EOL . $log; + } + } + + /** + * Log the run time of a proc method (one that doesn't use Process but does (use a function that does) a `proc_open()`). + */ + private static function log_proc_method_run_time( $key, $start_time ) { + $run_time = microtime( true ) - $start_time; + if ( ! isset( self::$proc_method_run_times[ $key ] ) ) { + self::$proc_method_run_times[ $key ] = array( 0, 0 ); + } + self::$proc_method_run_times[ $key ][0] += $run_time; + self::$proc_method_run_times[ $key ][1]++; + } + +} diff --git a/features/bootstrap/support.php b/features/bootstrap/support.php new file mode 100644 index 000000000..6aa17c6c3 --- /dev/null +++ b/features/bootstrap/support.php @@ -0,0 +1,200 @@ + $value ) { + if ( ! compareContents( $value, $actual->$name ) ) + return false; + } + } else if ( is_array( $expected ) ) { + foreach ( $expected as $key => $value ) { + if ( ! compareContents( $value, $actual[$key] ) ) + return false; + } + } else { + return $expected === $actual; + } + + return true; +} + +/** + * Compare two strings containing JSON to ensure that @a $actualJson contains at + * least what the JSON string @a $expectedJson contains. + * + * @return whether or not @a $actualJson contains @a $expectedJson + * @retval true @a $actualJson contains @a $expectedJson + * @retval false @a $actualJson does not contain @a $expectedJson + * + * @param[in] $actualJson the JSON string to be tested + * @param[in] $expectedJson the expected JSON string + * + * Examples: + * expected: {'a':1,'array':[1,3,5]} + * + * 1 ) + * actual: {'a':1,'b':2,'c':3,'array':[1,2,3,4,5]} + * return: true + * + * 2 ) + * actual: {'b':2,'c':3,'array':[1,2,3,4,5]} + * return: false + * element 'a' is missing from the root object + * + * 3 ) + * actual: {'a':0,'b':2,'c':3,'array':[1,2,3,4,5]} + * return: false + * the value of element 'a' is not 1 + * + * 4 ) + * actual: {'a':1,'b':2,'c':3,'array':[1,2,4,5]} + * return: false + * the contents of 'array' does not include 3 + */ +function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { + $actualValue = json_decode( $actualJson ); + $expectedValue = json_decode( $expectedJson ); + + if ( !$actualValue ) { + return false; + } + + return compareContents( $expectedValue, $actualValue ); +} + +/** + * Compare two strings to confirm $actualCSV contains $expectedCSV + * Both strings are expected to have headers for their CSVs. + * $actualCSV must match all data rows in $expectedCSV + * + * @param string A CSV string + * @param array A nested array of values + * @return bool Whether $actualCSV contains $expectedCSV + */ +function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { + $actualCSV = array_map( 'str_getcsv', explode( PHP_EOL, $actualCSV ) ); + + if ( empty( $actualCSV ) ) + return false; + + // Each sample must have headers + $actualHeaders = array_values( array_shift( $actualCSV ) ); + $expectedHeaders = array_values( array_shift( $expectedCSV ) ); + + // Each expectedCSV must exist somewhere in actualCSV in the proper column + $expectedResult = 0; + foreach ( $expectedCSV as $expected_row ) { + $expected_row = array_combine( $expectedHeaders, $expected_row ); + foreach ( $actualCSV as $actual_row ) { + + if ( count( $actualHeaders ) != count( $actual_row ) ) + continue; + + $actual_row = array_intersect_key( array_combine( $actualHeaders, $actual_row ), $expected_row ); + if ( $actual_row == $expected_row ) + $expectedResult++; + } + } + + return $expectedResult >= count( $expectedCSV ); +} + +/** + * Compare two strings containing YAML to ensure that @a $actualYaml contains at + * least what the YAML string @a $expectedYaml contains. + * + * @return whether or not @a $actualYaml contains @a $expectedJson + * @retval true @a $actualYaml contains @a $expectedJson + * @retval false @a $actualYaml does not contain @a $expectedJson + * + * @param[in] $actualYaml the YAML string to be tested + * @param[in] $expectedYaml the expected YAML string + */ +function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) { + $actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml ); + $expectedValue = Mustangostang\Spyc::YAMLLoad( $expectedYaml ); + + if ( !$actualValue ) { + return false; + } + + return compareContents( $expectedValue, $actualValue ); +} + diff --git a/features/class-wp-cli.feature b/features/class-wp-cli.feature deleted file mode 100644 index d344bd47a..000000000 --- a/features/class-wp-cli.feature +++ /dev/null @@ -1,47 +0,0 @@ -Feature: Various utilities for WP-CLI commands - - @skip-windows - Scenario Outline: Check that `proc_open()` and `proc_close()` aren't disabled for `WP_CLI::launch()` - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=} --skip-wordpress eval "WP_CLI::launch( null );"` - Then STDERR should contain: - """ - Error: Cannot do 'launch': The PHP functions `proc_open()` and/or `proc_close()` are disabled - """ - And STDOUT should be empty - And the return code should be 1 - - Examples: - | func | - | proc_open | - | proc_close | - - Scenario: HTTP URL scheme clears pre-existing HTTPS server variable - Given an empty directory - And a test.php file: - """ - @@ -143,15 +126,11 @@ Feature: `wp cli completions` tasks """ And STDOUT should contain: """ - config + plugin """ And STDOUT should contain: """ - core - """ - And STDOUT should contain: - """ - eval + server """ And STDERR should be empty And the return code should be 0 @@ -169,10 +148,6 @@ Feature: `wp cli completions` tasks """ @example """ - And STDOUT should contain: - """ - config - """ And STDOUT should contain: """ core @@ -184,19 +159,15 @@ Feature: `wp cli completions` tasks And STDERR should be empty And the return code should be 0 - When I run `wp cli completions --line="wp @example core " --point=100` + When I run `wp cli completions --line="wp @example plugin " --point=100` Then STDOUT should contain: """ - install - """ - And STDOUT should contain: - """ - update + list """ And STDERR should be empty And the return code should be 0 - When I run `wp cli completions --line="wp help core " --point=100` + When I run `wp cli completions --line="wp help language core " --point=100` Then STDOUT should contain: """ install @@ -215,15 +186,7 @@ Feature: `wp cli completions` tasks """ And STDOUT should contain: """ - config - """ - And STDOUT should contain: - """ - core - """ - And STDOUT should contain: - """ - eval + post-type """ And STDERR should be empty And the return code should be 0 @@ -247,7 +210,7 @@ Feature: `wp cli completions` tasks Scenario: Bash Completion for global parameters Given an empty directory - When I run `wp cli completions --line="wp core download " --point=100` + When I run `wp cli completions --line="wp plugin list " --point=100` Then STDOUT should contain: """ --path= @@ -319,162 +282,18 @@ Feature: `wp cli completions` tasks And STDERR should be empty And the return code should be 0 - When I run `wp cli completions --line="wp core download --path --p" --point=100` + When I run `wp cli completions --line="wp plugin list --path --p" --point=100` Then STDOUT should contain: """ --prompt= """ - And STDOUT should not contain: + Then STDOUT should not contain: """ --path """ - When I run `wp cli completions --line="wp core download --no-color" --point=100` - Then STDOUT should contain: + When I run `wp cli completions --line="wp plugin list --no-color" --point=100` + Then STDOUT should not contain: """ --no-color """ - - When I run `wp cli completions --line="wp core download --no-color --no-color" --point=100` - Then STDOUT should be empty - - Scenario: Bash Completion for global --url parameter in subdirectory installation - Given a WP multisite subdirectory installation - And I run `wp site create --slug=foo` - And I run `wp site create --slug=foot` - And I run `wp site create --slug=football` - And I run `wp site create --slug=bar` - And I run `wp site create --slug=baz` - And I run `wp site create --slug=waldo` - - # show all matches - When I run `wp cli completions --line="wp plugin list --url=fo" --point=100` - Then STDOUT should contain: - """ - foo - """ - And STDOUT should contain: - """ - foot - """ - And STDOUT should contain: - """ - football - """ - - When I run `wp cli completions --line="wp plugin list --url=https://example.com/bar" --point=100` - Then STDOUT should contain: - """ - https://example.com/bar/ - """ - - Scenario: Bash Completion for global --url parameter in subdomain installation - Given a WP multisite subdomain installation - And I run `wp site create --slug=foo` - And I run `wp site create --slug=foot` - And I run `wp site create --slug=football` - And I run `wp site create --slug=bar` - And I run `wp site create --slug=baz` - And I run `wp site create --slug=waldo` - - # show all matches - When I run `wp cli completions --line="wp plugin list --url=fo" --point=100` - Then STDOUT should contain: - """ - foo.example.com - """ - And STDOUT should contain: - """ - foot.example.com - """ - And STDOUT should contain: - """ - football.example.com - """ - - When I run `wp cli completions --line="wp plugin list --url=http://bar" --point=100` - Then STDOUT should contain: - """ - http://bar.example.com - """ - - Scenario: Bash Completion for flag values with enum options - Given an empty directory - - When I run `wp cli completions --line="wp cli check-update --format=" --point=100` - Then STDOUT should contain: - """ - table - """ - And STDOUT should contain: - """ - csv - """ - And STDOUT should contain: - """ - json - """ - And STDOUT should contain: - """ - yaml - """ - And STDOUT should contain: - """ - count - """ - And STDERR should be empty - And the return code should be 0 - - When I run `wp cli completions --line="wp cli check-update --format=j" --point=100` - Then STDOUT should contain: - """ - json - """ - And STDOUT should not contain: - """ - table - """ - And STDOUT should not contain: - """ - csv - """ - And STDERR should be empty - And the return code should be 0 - - When I run `wp cli completions --line="wp cli info --format=" --point=100` - Then STDOUT should contain: - """ - list - """ - And STDOUT should contain: - """ - json - """ - And STDERR should be empty - And the return code should be 0 - - When I run `wp cli completions --line="wp cli check-update --format=c" --point=100` - Then STDOUT should contain: - """ - csv - """ - And STDOUT should contain: - """ - count - """ - And STDOUT should not contain: - """ - json - """ - And STDERR should be empty - And the return code should be 0 - - When I run `wp cli completions --line="wp cli check-update --format=xyz" --point=100` - Then STDOUT should be empty - And STDERR should be empty - And the return code should be 0 - - When I run `wp cli completions --line="wp cli check-update --field=" --point=100` - Then STDOUT should be empty - And STDERR should be empty - And the return code should be 0 diff --git a/features/cli-cache.feature b/features/cli-cache.feature deleted file mode 100644 index f5ef9c9e2..000000000 --- a/features/cli-cache.feature +++ /dev/null @@ -1,134 +0,0 @@ -Feature: CLI Cache - - Scenario: Remove all files from cache directory - Given an empty cache - - When I run `wp core download --path={CACHE_DIR} --version=6.9 --force` - And I run `wp core download --path={CACHE_DIR} --version=6.9 --force --locale=de_DE` - Then the {SUITE_CACHE_DIR}/core directory should contain: - """ - wordpress-6.9-de_DE.zip - wordpress-6.9-en_US.zip - """ - - When I run `wp cli cache clear` - Then STDOUT should be: - """ - Success: Cache cleared. - """ - And STDERR should be empty - And the {SUITE_CACHE_DIR}/core directory should not contain: - """ - wordpress-6.9-de_DE.zip - """ - And the {SUITE_CACHE_DIR}/core directory should not contain: - """ - wordpress-6.9-en_US.zip - """ - - Scenario: Using a null device disables the cache without throwing an error - Given an empty directory - And a env-var.php file: - """ - - * : Configuration parameter name to check for. - * - * @when after_wp_load - */ - public function __invoke( $args ) { - if ( WP_CLI::has_config( $args[0] ) ) { - WP_CLI::log( "Global configuration '{$args[0]}' does exist." ); - } else { - WP_CLI::log( "Global configuration '{$args[0]}' does not exist." ); - } - } - } - WP_CLI::add_command( 'custom-command', 'Custom_Command' ); + WP-CLI version + """ + And STDOUT should not contain: + """ + 0.0.0 """ - When I run `wp --require=custom-cmd.php custom-command url` + When I run `{PHAR_PATH} cli update` Then STDOUT should be: """ - Global configuration 'url' does exist. + Success: WP-CLI is at the latest version. """ - When I run `wp --require=custom-cmd.php custom-command dummy` + @github-api + Scenario: Patch update from 0.14.0 to 0.14.1 + Given an empty directory + And a new Phar with version "0.14.0" + + When I run `{PHAR_PATH} --version` Then STDOUT should be: """ - Global configuration 'dummy' does not exist. + WP-CLI 0.14.0 """ - Scenario: Dump command list with alias included - Given a WP installation - And a custom-cmd-with-alias.php file: + When I run `{PHAR_PATH} cli update --patch --yes` + Then STDOUT should contain: """ - + """ + + When I run `wp cap --help` + Then STDOUT should contain: + """ + wp cap + """ + + When I run `wp checksum --help` + Then STDOUT should contain: + """ + wp checksum + """ + + When I run `wp comment --help` + Then STDOUT should contain: + """ + wp comment + """ + + When I run `wp config --help` + Then STDOUT should contain: + """ + wp config + """ + + When I run `wp core --help` + Then STDOUT should contain: + """ + wp core + """ + + When I run `wp cron --help` + Then STDOUT should contain: + """ + wp cron + """ + + When I run `wp cron` + Then STDOUT should contain: + """ + usage: wp cron event + or: wp cron schedule + or: wp cron test + """ + + When I run `wp db --help` + Then STDOUT should contain: + """ + wp db + """ + + When I run `wp db` + Then STDOUT should contain: + """ + or: wp db cli + """ + + When I run `wp eval --help` + Then STDOUT should contain: + """ + wp eval + """ + + When I run `wp eval-file --help` + Then STDOUT should contain: + """ + wp eval-file [...] + """ + + When I run `wp export --help` + Then STDOUT should contain: + """ + wp export [--dir=] + """ + When I run `wp help --help` Then STDOUT should contain: """ wp help [...] """ + When I run `wp import --help` + Then STDOUT should contain: + """ + wp import ... --authors= + """ + + When I run `wp language --help` + Then STDOUT should contain: + """ + wp language + """ + + When I run `wp media --help` + Then STDOUT should contain: + """ + wp media + """ + + When I run `wp media` + Then STDOUT should contain: + """ + or: wp media regenerate + """ + + When I run `wp menu --help` + Then STDOUT should contain: + """ + wp menu + """ + + When I run `wp network --help` + Then STDOUT should contain: + """ + wp network + """ + + When I run `wp option --help` + Then STDOUT should contain: + """ + wp option + """ + + When I run `wp package --help` + Then STDOUT should contain: + """ + wp package + """ + + When I run `wp package` + Then STDOUT should contain: + """ + or: wp package install + """ + + When I run `wp plugin --help` + Then STDOUT should contain: + """ + wp plugin + """ + + When I run `wp post --help` + Then STDOUT should contain: + """ + wp post + """ + + When I run `wp post-type --help` + Then STDOUT should contain: + """ + wp post-type + """ + + When I run `wp rewrite --help` + Then STDOUT should contain: + """ + wp rewrite + """ + + When I run `wp role --help` + Then STDOUT should contain: + """ + wp role + """ + + When I run `wp scaffold --help` + Then STDOUT should contain: + """ + wp scaffold + """ + + When I run `wp search-replace --help` + Then STDOUT should contain: + """ + wp search-replace + """ + + When I run `wp server --help` + Then STDOUT should contain: + """ + wp server [--host=] + """ + + When I run `wp shell --help` + Then STDOUT should contain: + """ + wp shell [--basic] + """ + + When I run `wp sidebar --help` + Then STDOUT should contain: + """ + wp sidebar + """ + + When I run `wp site --help` + Then STDOUT should contain: + """ + wp site + """ + + When I run `wp super-admin --help` + Then STDOUT should contain: + """ + wp super-admin + """ + + When I run `wp taxonomy --help` + Then STDOUT should contain: + """ + wp taxonomy + """ + + When I run `wp term --help` + Then STDOUT should contain: + """ + wp term + """ + + When I run `wp theme --help` + Then STDOUT should contain: + """ + wp theme + """ + + When I run `wp transient --help` + Then STDOUT should contain: + """ + wp transient + """ + + When I run `wp user --help` + Then STDOUT should contain: + """ + wp user + """ + + When I run `wp widget --help` + Then STDOUT should contain: + """ + wp widget + """ + Scenario: Invalid class is specified for a command Given an empty directory And a custom-cmd.php file: @@ -27,14 +269,6 @@ Feature: WP-CLI Commands Scenario: Invalid subcommand of valid command Given an empty directory - And a session_no file: - """ - n - """ - And a session_yes file: - """ - y - """ And a custom-cmd.php file: """ prefix = $prefix; } @@ -244,7 +478,7 @@ Feature: WP-CLI Commands """ message = $message; } @@ -401,15 +635,6 @@ Feature: WP-CLI Commands Invalid value specified for 'meal' (A type of meal.) """ - When I try `wp foo hello --apple=fuji --meal=breakfast,lunch,dinner` - Then STDERR should be empty - - When I try `wp foo hello --apple=fuji --meal=breakfast,snack,dinner` - Then STDERR should contain: - """ - Invalid value specified for 'meal' (A type of meal.) - """ - When I run `wp foo hello --apple=fuji` Then STDOUT should be: """ @@ -532,7 +757,7 @@ Feature: WP-CLI Commands WP_CLI::add_command( 'foo', 'foo', array( 'shortdesc' => 'My awesome function command', 'when' => 'before_wp_load', - 'longdesc' => '## EXAMPLES' . PHP_EOL . PHP_EOL . ' # Run the custom foo command', + 'longdesc' => '## EXAMPLES ' . PHP_EOL . PHP_EOL . ' # Run the custom foo command', ) ); """ And a wp-cli.yml file: @@ -544,168 +769,11 @@ Feature: WP-CLI Commands When I run `wp help foo` Then STDOUT should contain: """ - NAME - - wp foo - - DESCRIPTION - - My awesome function command - - SYNOPSIS - - wp foo - - EXAMPLES - - # Run the custom foo command - - GLOBAL PARAMETERS - - """ - - # With synopsis, appended. - Given a hello-command.php file: - """ - 'Prints a greeting.', - 'synopsis' => array( - array( - 'type' => 'positional', - 'name' => 'name', - 'description' => 'Name of person to greet.', - 'optional' => false, - 'repeating' => false, - ), - array( - 'type' => 'assoc', - 'name' => 'type', - 'optional' => true, - 'default' => 'success', - 'options' => array( 'success', 'error' ), - ), - array( - 'type' => 'flag', - 'name' => 'honk', - 'optional' => true, - ), - ), - 'when' => 'after_wp_load', - 'longdesc' => "\r\n## EXAMPLES\n\n# Say hello to Newman\nwp example hello Newman\nSuccess: Hello, Newman!", - ) ); - """ - - When I run `wp --require=hello-command.php help example hello` - Then STDOUT should contain: - """ - NAME - - wp example hello - - DESCRIPTION - - Prints a greeting. - - SYNOPSIS - - wp example hello [--type=] [--honk] - - OPTIONS - - - Name of person to greet. - - [--type=] - --- - default: success - options: - - success - - error - --- - - [--honk] - EXAMPLES - - # Say hello to Newman - wp example hello Newman - Success: Hello, Newman! - - GLOBAL PARAMETERS - """ - - Given a test-reordering.php file: + And STDOUT should contain: """ - 'Test reordering of arguments.', - 'synopsis' => [ - [ - 'type' => 'flag', - 'name' => 'my-flag', - 'description' => 'Flag something', - ], - [ - 'type' => 'assoc', - 'name' => 'my-assoc', - 'description' => 'Assoc something', - 'options' => [ 'a', 'b', 'c' ], - 'default' => 'a', - ], - [ - 'type' => 'positional', - 'name' => 'my-positional', - 'description' => 'Positional something', - 'optional' => false, - 'repeating' => false, - ], - ], - 'when' => 'before_wp_load', - ] ); - """ - - When I run `wp --require=test-reordering.php help test-reordering` - Then STDOUT should contain: - """ - NAME - - wp test-reordering - - DESCRIPTION - - Test reordering of arguments. - - SYNOPSIS - - wp test-reordering --my-assoc= --my-flag - - OPTIONS - - - Positional something - - --my-assoc= - Assoc something - --- - default: a - options: - - a - - b - - c - --- - - --my-flag - Flag something + # Run the custom foo command """ Scenario: Register a command with default and accepted arguments. @@ -780,7 +848,7 @@ Feature: WP-CLI Commands """ And STDERR should be empty - When I run `wp --require=test-cmd.php foo ""` + When I run `wp --require=test-cmd.php foo ''` Then STDOUT should be YAML containing: """ bar: @@ -805,7 +873,7 @@ Feature: WP-CLI Commands Invalid value specified for 'burrito' (This is the burrito argument.) """ - When I try `wp --require=test-cmd.php foo apple --burrito=""` + When I try `wp --require=test-cmd.php foo apple --burrito=''` Then STDERR should contain: """ Error: Parameter errors: @@ -818,13 +886,13 @@ Feature: WP-CLI Commands Error: Invalid value specified for positional arg. """ - When I try `wp --require=test-cmd.php foo apple "cha cha cha" taco_del_mar` + When I try `wp --require=test-cmd.php foo apple 'cha cha cha' taco_del_mar` Then STDERR should contain: """ Error: Invalid value specified for positional arg. """ - When I run `wp --require=test-cmd.php foo apple "cha cha cha"` + When I run `wp --require=test-cmd.php foo apple 'cha cha cha'` Then STDOUT should be YAML containing: """ bar: apple @@ -956,7 +1024,7 @@ Feature: WP-CLI Commands """ message = $message; } @@ -983,155 +1051,43 @@ Feature: WP-CLI Commands Scenario: WP-CLI suggests matching commands when user entry contains typos Given a WP installation - And a session_no file: - """ - n - """ - When I try `wp clu < session_no` + When I try `wp clu` Then STDERR should contain: """ - Warning: 'clu' is not a registered wp command - """ - And STDOUT should contain: - """ - Did you mean 'cli'? [y/n] + Did you mean 'cli'? """ - When I try `wp cli nfo < session_no` + When I try `wp cli nfo` Then STDERR should contain: """ - Warning: 'nfo' is not a registered subcommand of 'cli' - """ - And STDOUT should contain: - """ - Did you mean 'info'? [y/n] + Did you mean 'info'? """ When I try `wp cli beyondlevenshteinthreshold` - Then STDOUT should not contain: + Then STDERR should not contain: """ Did you mean """ - Scenario: WP-CLI optionally runs matching commands when user entry contains typos - Given a WP installation - And a session_yes file: - """ - y - """ + Scenario: WP-CLI suggests matching parameters when user entry contains typos + Given an empty directory - When I try `wp clu < session_yes` + When I try `wp cli info --quie` Then STDERR should contain: """ - Warning: 'clu' is not a registered wp command - """ - And STDOUT should contain: - """ - Did you mean 'cli'? - """ - And STDOUT should contain: - """ - See 'wp help cli ' for more information on a specific command. + Did you mean '--quiet'? """ - When I try `wp cli nfo < session_yes` + When I try `wp cli info --forma=json` Then STDERR should contain: """ - Warning: 'nfo' is not a registered subcommand of 'cli' + Did you mean '--format'? """ - And STDOUT should contain: - """ - Did you mean 'info'? - """ - And STDOUT should contain: - """ - WP-CLI version: - """ - - When I try `wp cli beyondlevenshteinthreshold` - Then STDERR should not contain: - """ - Did you mean - """ - - Scenario: WP-CLI automatically runs matching commands when user entry contains typos - Given a WP installation - - When I try `WP_CLI_AUTOCORRECT=1 wp clu` - Then STDERR should not contain: - """ - Warning: 'clu' is not a registered wp command - """ - And STDOUT should not contain: - """ - Did you mean 'cli'? - """ - And STDOUT should contain: - """ - See 'wp help cli ' for more information on a specific command. - """ - - When I try `WP_CLI_AUTOCORRECT=1 wp cli nfo` - Then STDERR should not contain: - """ - Warning: 'nfo' is not a registered subcommand of 'cli' - """ - And STDOUT should not contain: - """ - Did you mean 'info'? - """ - And STDOUT should contain: - """ - WP-CLI version: - """ - - When I try `WP_CLI_AUTOCORRECT=1 wp hel post mota` - Then STDERR should not contain: - """ - is not a registered wp command - """ - And STDERR should not contain: - """ - is not a registered subcommand - """ - And STDOUT should not contain: - """ - Did you mean - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: - """ - wp post meta - """ - - When I try `wp cli beyondlevenshteinthreshold` - Then STDERR should not contain: - """ - Did you mean - """ - - Scenario: WP-CLI suggests matching parameters when user entry contains typos - Given an empty directory - - When I try `wp cli info --quie` - Then STDERR should contain: - """ - Did you mean '--quiet'? - """ - - When I try `wp cli info --forma=json` - Then STDERR should contain: - """ - Did you mean '--format'? - """ - - Scenario: Adding a command can be aborted through the hooks system - Given an empty directory - And a abort-add-command.php file: + + Scenario: Adding a command can be aborted through the hooks system + Given an empty directory + And a abort-add-command.php file: """ &1` - Then STDOUT should contain: - """ - Adding namespace: my-namespace - """ - And STDOUT should contain: - """ - Adding command: my-command - """ - Scenario: Late-registered command should appear in command usage Given a WP installation And a test-cmd.php file: @@ -1633,15 +1560,13 @@ Feature: WP-CLI Commands - test-cmd.php """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help core` + When I run `wp help core` Then STDOUT should contain: """ custom-subcommand """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp core` + When I run `wp core` Then STDOUT should contain: """ usage: @@ -1654,364 +1579,3 @@ Feature: WP-CLI Commands """ core custom-subcommand """ - - Scenario: An activated plugin should successfully add custom commands when hooked on the cli_init action - Given a WP installation - And a wp-content/plugins/custom-command/custom-cmd.php file: - """ - ... - * : One or more IDs - * - * @when before_wp_load - */ - function test_ids_command( $args ) { - WP_CLI::log( 'Number of arguments: ' . count( $args ) ); - foreach ( $args as $id ) { - WP_CLI::log( 'ID: ' . $id ); - } - } - WP_CLI::add_command( 'test-ids', 'test_ids_command' ); - """ - - When I run `wp --require=custom-cmd.php test-ids "123 456 789"` - Then STDOUT should contain: - """ - Number of arguments: 3 - """ - And STDOUT should contain: - """ - ID: 123 - """ - And STDOUT should contain: - """ - ID: 456 - """ - And STDOUT should contain: - """ - ID: 789 - """ - - When I run `wp --require=custom-cmd.php test-ids 123` - Then STDOUT should contain: - """ - Number of arguments: 1 - """ - And STDOUT should contain: - """ - ID: 123 - """ - - When I run `wp --require=custom-cmd.php test-ids "hello world"` - Then STDOUT should contain: - """ - Number of arguments: 1 - """ - And STDOUT should contain: - """ - ID: hello world - """ - - Scenario: Warn when command overrides global argument - Given an empty directory - And a custom-cmd.php file: - """ - - * : User argument that conflicts with global - * - * [--quiet] - * : Quiet flag that conflicts with global - * - * @when before_wp_load - */ - $foo = function( $args, $assoc_args ) { - WP_CLI::success( 'Command executed' ); - }; - WP_CLI::add_command( 'multiconflict', $foo ); - """ - - When I try `wp --require=custom-cmd.php help` - Then STDERR should contain: - """ - Warning: The `multiconflict` command is registering an argument '--user' that conflicts with a global argument of the same name. - """ - And STDERR should contain: - """ - Warning: The `multiconflict` command is registering an argument '--quiet' that conflicts with a global argument of the same name. - """ - - Scenario: No warning when command uses non-conflicting arguments - Given an empty directory - And a custom-cmd.php file: - """ - - * : User argument that conflicts - */ - public function with_user( $args, $assoc_args ) { - WP_CLI::success( 'Method executed' ); - } - - /** - * Method with no conflicts - * - * ## OPTIONS - * - * [--custom] - * : Custom flag - */ - public function clean( $args, $assoc_args ) { - WP_CLI::success( 'Method executed' ); - } - } - WP_CLI::add_command( 'testcmd', 'TestCommand' ); - """ - - When I try `wp --require=custom-cmd.php help` - Then STDERR should contain: - """ - Warning: The `testcmd with_debug` command is registering an argument '--debug' that conflicts with a global argument of the same name. - """ - And STDERR should contain: - """ - Warning: The `testcmd with_user` command is registering an argument '--user' that conflicts with a global argument of the same name. - """ - - Scenario: Skip global argument conflict warning with annotation - Given an empty directory - And a custom-cmd.php file: - """ - - """ + When I run `wp` from 'wp-content' + Then STDOUT should not be empty Scenario: WP in a subdirectory Given a WP installation in 'foo' @@ -58,38 +54,35 @@ Feature: Have a config file When I run `wp core is-installed` from 'foo/wp-content' Then STDOUT should be empty - Given an empty other/subdir directory + When I run `mkdir -p other/subdir` And I run `wp core is-installed` from 'other/subdir' Then STDOUT should be empty Scenario: WP in a subdirectory (autodetected) Given a WP installation in 'foo' - And an index.php file: - """ - require('./foo/wp-blog-header.php'); - """ + Given an index.php file: + """ + require('./foo/wp-blog-header.php'); + """ When I run `wp core is-installed` Then STDOUT should be empty Given an index.php file: - """ - require dirname(__FILE__) . '/foo/wp-blog-header.php'; - """ + """ + require dirname(__FILE__) . '/foo/wp-blog-header.php'; + """ When I run `wp core is-installed` Then STDOUT should be empty - Given an empty other/subdir directory - And a other/subdir/index.php file: - """ - other/subdir/index.php` And I run `wp core is-installed` from 'other/subdir' Then STDOUT should be empty - Scenario: Nested installations + Scenario: Nested installs Given a WP installation - And a WP installation in 'foo' + And a WP install in 'foo' And a wp-cli.yml file: """ """ @@ -109,44 +102,29 @@ Feature: Have a config file - core multisite-convert """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `WP_CLI_CONFIG_PATH=config.yml wp` - Then STDOUT should contain: + When I run `WP_CLI_CONFIG_PATH=config.yml wp` + Then STDOUT should not contain: """ eval-file """ - And STDOUT should contain: - """ - Disabled via configuration file - """ When I try `WP_CLI_CONFIG_PATH=config.yml wp help eval-file` - Then STDERR should contain: + Then STDERR should be: """ Error: The 'eval-file' command has been disabled from the config file. """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `WP_CLI_CONFIG_PATH=config.yml wp core` - Then STDOUT should contain: + When I run `WP_CLI_CONFIG_PATH=config.yml wp core` + Then STDOUT should not contain: """ or: wp core multisite-convert """ - And STDOUT should contain: - """ - Disabled via configuration file - """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `WP_CLI_CONFIG_PATH=config.yml wp help core` - Then STDOUT should contain: + When I run `WP_CLI_CONFIG_PATH=config.yml wp help core` + Then STDOUT should not contain: """ multisite-convert """ - And STDOUT should contain: - """ - Disabled via configuration file - """ When I try `WP_CLI_CONFIG_PATH=config.yml wp core multisite-convert` Then STDERR should contain: @@ -160,35 +138,6 @@ Feature: Have a config file Error: The 'core multisite-convert' command has been disabled from the config file. """ - Scenario: Disabled commands inheritance with merge - Given a WP installation - And a prod.yml file: - """ - disabled_commands: - - eval - """ - And a wp-cli.yml file: - """ - disabled_commands: - - eval-file - - cache - _: - merge: true - inherit: prod.yml - """ - - When I try `wp cli has-command eval` - Then the return code should be 1 - - When I try `wp cli has-command eval-file` - Then the return code should be 1 - - When I try `wp cli has-command cache` - Then the return code should be 1 - - When I run `wp cli has-command core` - Then the return code should be 0 - Scenario: 'core config' parameters Given an empty directory And WP files @@ -241,46 +190,24 @@ Feature: Have a config file administrator """ - @skip-windows Scenario: Command-specific configs Given a WP installation And a wp-cli.yml file: """ eval: foo: bar + post list: + format: count """ # Arbitrary values should be passed, without warnings - When I run `wp eval "echo json_encode( \$assoc_args );"` - Then STDOUT should be JSON containing: - """ - {"foo": "bar"} - """ - - @require-windows - Scenario: Command-specific configs (Windows) - Given a WP installation - And a wp-cli.yml file: - """ - eval: - foo: bar - """ - - # Arbitrary values should be passed, without warnings - When I run `wp eval "echo json_encode( $assoc_args );"` + When I run `wp eval 'echo json_encode( $assoc_args );'` Then STDOUT should be JSON containing: """ {"foo": "bar"} """ - Scenario: CLI args should trump config values - Given a WP installation - And a wp-cli.yml file: - """ - post list: - format: count - """ - + # CLI args should trump config values When I run `wp post list` Then STDOUT should be a number When I run `wp post list --format=json` @@ -305,7 +232,7 @@ Feature: Have a config file """ When I run `WP_CLI_CONFIG_PATH=test-dir/config.yml wp help` - Then STDERR should be empty + Then STDERR should be empty Scenario: Load WordPress with `--debug` Given a WP installation @@ -315,7 +242,7 @@ Feature: Have a config file """ No readable global config found """ - And STDERR should contain: + Then STDERR should contain: """ No project config found """ @@ -342,7 +269,7 @@ Feature: Have a config file """ No readable global config found """ - And STDERR should contain: + Then STDERR should contain: """ No project config found """ @@ -369,7 +296,7 @@ Feature: Have a config file """ No readable global config found """ - And STDERR should not contain: + Then STDERR should not contain: """ No project config found """ @@ -394,10 +321,10 @@ Feature: Have a config file Scenario: Missing required files should not fatal WP-CLI Given an empty directory And a wp-cli.yml file: - """ - require: - - missing-file.php - """ + """ + require: + - missing-file.php + """ When I try `wp help` Then STDERR should contain: @@ -542,58 +469,8 @@ Feature: Have a config file {"bar":"burrito","apple":"apple"} """ - Scenario: Config inheritance in nested folders - Given an empty directory - And a wp-cli.local.yml file: - """ - @dev: - ssh: vagrant@example.test/srv/www/example.com/current - path: web/wp - """ - And a site/wp-cli.yml file: - """ - _: - inherit: ../wp-cli.local.yml - @otherdev: - ssh: vagrant@otherexample.test/srv/www/otherexample.com/current - """ - And a site/public/index.php file: - """ - wp-config.php` - - When I try `wp core is-installed` - Then STDERR should not contain: - """ - PHP Parse error: syntax error, unexpected '?' - """ - And STDERR should contain: - """ - Warning: UTF-8 byte-order mark (BOM) detected in wp-config.php file, stripping it for parsing. - """ - - Scenario: Strange wp-config.php file with missing wp-settings.php call - Given a WP installation - And a wp-config.php file: - """ - 'before_wp_load' ) ); - """ - And a wp-cli.yml file: - """ - env: - WP_CLI_CACHE_EXPIRY: 7200 - """ - - When I run `wp --require=test-cache-config.php test-cache-config` - Then STDOUT should contain: - """ - Cache expiry: 7200 - """ - - Scenario: Environment variables configured in wp-cli.yml - WP_CLI_CACHE_MAX_SIZE - Given an empty directory - And a test-cache-config.php file: - """ - 'before_wp_load' ) ); - """ - And a wp-cli.yml file: - """ - env: - WP_CLI_CACHE_MAX_SIZE: 209715200 - """ - - When I run `wp --require=test-cache-config.php test-cache-config` - Then STDOUT should contain: - """ - Cache max size: 209715200 - """ - - Scenario: WP_CLI_CACHE_EXPIRY and WP_CLI_CACHE_MAX_SIZE with environment variable precedence - Given an empty directory - And a test-cache-config.php file: - """ - 'before_wp_load' ) ); - """ - And a wp-cli.yml file: - """ - env: - WP_CLI_CACHE_EXPIRY: 3600 - WP_CLI_CACHE_MAX_SIZE: 104857600 - """ - - When I run `WP_CLI_CACHE_EXPIRY=7200 wp --require=test-cache-config.php test-cache-config` - Then STDOUT should contain: - """ - Cache expiry: 7200 - """ - And STDOUT should contain: - """ - Cache max size: 104857600 - """ - - Scenario: Environment variables configured in wp-cli.yml - WP_CLI_ERROR_RERUN - Given an empty directory - And a test-error-rerun.php file: - """ - 'before_wp_load' ) ); - """ - And a wp-cli.yml file: - """ - env: - WP_CLI_ERROR_RERUN: "no" - """ - - When I run `wp --require=test-error-rerun.php test-error-rerun` - Then STDOUT should contain: - """ - WP_CLI_ERROR_RERUN: no - """ - - Scenario: Environment variables configured in wp-cli.yml - WP_CLI_AUTO_UPDATE_PROMPT - Given an empty directory - And a test-auto-update.php file: - """ - 'before_wp_load' ) ); - """ - And a wp-cli.yml file: - """ - env: - WP_CLI_AUTO_UPDATE_PROMPT: "no" - """ - - When I run `wp --require=test-auto-update.php test-auto-update` - Then STDOUT should contain: - """ - WP_CLI_AUTO_UPDATE_PROMPT: no - """ - - Scenario: Environment variables configured in wp-cli.yml - WP_CLI_AUTOCORRECT - Given an empty directory - And a test-autocorrect.php file: - """ - 'before_wp_load' ) ); - """ - And a wp-cli.yml file: - """ - env: - WP_CLI_AUTOCORRECT: "1" - """ - - When I run `wp --require=test-autocorrect.php test-autocorrect` - Then STDOUT should contain: - """ - WP_CLI_AUTOCORRECT: 1 - """ - - Scenario: Custom system config path via WP_CLI_SYSTEM_SETTINGS_PATH - Given an empty directory - And a system-config.yml file: - """ - disabled_commands: - - eval - """ - And a test-cmd.php file: - """ - 'before_wp_load' ) ); - """ - - When I try `WP_CLI_SYSTEM_SETTINGS_PATH=system-config.yml wp --require=test-cmd.php test-cmd --debug` - Then STDERR should contain: - """ - Using system config from WP_CLI_SYSTEM_SETTINGS_PATH env var: - """ - And STDERR should contain: - """ - system-config.yml - """ - - When I try `WP_CLI_SYSTEM_SETTINGS_PATH=system-config.yml wp eval "echo 'test';"` - Then STDERR should contain: - """ - Error: The 'eval' command has been disabled from the config file. - """ - - Scenario: System config with aliases via WP_CLI_SYSTEM_SETTINGS_PATH - Given an empty directory - And a system-config.yml file: - """ - @system-alias: - ssh: user@example.com/var/www - """ - - When I run `WP_CLI_SYSTEM_SETTINGS_PATH=system-config.yml wp cli alias list` - Then STDOUT should contain: - """ - @system-alias: - """ - And STDOUT should contain: - """ - ssh: user@example.com/var/www - """ - - Scenario: System config overridden by user config - Given a WP installation - And a system-config.yml file: - """ - @system-alias: - ssh: user@example.com/var/www/foo - """ - And a user-config.yml file: - """ - @system-alias: - ssh: user@example.com/var/www/bar - """ - - When I run `WP_CLI_SYSTEM_SETTINGS_PATH=system-config.yml WP_CLI_CONFIG_PATH=user-config.yml wp cli alias list` - Then STDOUT should contain: - """ - @system-alias: - """ - And STDOUT should contain: - """ - ssh: user@example.com/var/www/bar + http://example.com """ diff --git a/features/context.feature b/features/context.feature deleted file mode 100644 index 3bd7b8715..000000000 --- a/features/context.feature +++ /dev/null @@ -1,340 +0,0 @@ -Feature: Context handling via --context global flag - - Scenario: Auto context is the default - Given a WP install - And a context-logger.php file: - """ - context_manager->get_context(); - WP_CLI::log( "Current context: {$context}" ); - } ); - """ - - When I run `wp --require=context-logger.php eval ""` - Then the return code should be 0 - And STDOUT should contain: - """ - Current context: cli - """ - - When I run `wp --require=context-logger.php plugin list` - Then the return code should be 0 - And STDOUT should contain: - """ - Current context: admin - """ - - Scenario: CLI context can be selected - Given a WP install - - When I run `wp --context=cli eval "var_export( is_admin() );"` - Then the return code should be 0 - And STDOUT should be: - """ - false - """ - - When I run `wp --context=cli eval "var_export( function_exists( \"media_handle_upload\" ) );"` - Then the return code should be 0 - And STDOUT should be: - """ - true - """ - - When I run `wp --context=cli eval "add_action( \"admin_init\", static function () { WP_CLI::warning( \"admin_init was triggered.\" ); } );"` - Then the return code should be 0 - And STDERR should not contain: - """ - admin_init was triggered. - """ - - Scenario: Admin context can be selected - Given a WP install - - When I run `wp --context=admin eval "var_export( is_admin() );"` - Then the return code should be 0 - And STDOUT should be: - """ - true - """ - - When I run `wp --context=admin eval "var_export( function_exists( \"media_handle_upload\" ) );"` - Then the return code should be 0 - And STDOUT should be: - """ - true - """ - - When I run `wp eval --context=admin "add_action( \"admin_init\", static function () { WP_CLI::warning( \"admin_init was triggered.\" ); } );"` - Then the return code should be 0 - And STDERR should not contain: - """ - admin_init was triggered. - """ - - Scenario: No warnings for custom menu order in admin context. - Given a WP install - And a custom-menu-order.php file: - """ - context_manager->get_context(); - WP_CLI::log( "Current context: {$context}" ); - } ); - """ - - When I run `wp --require=context-logger.php --context=auto post list` - Then the return code should be 0 - And STDOUT should contain: - """ - Current context: cli - """ - - When I run `wp --require=context-logger.php --context=auto plugin list` - Then the return code should be 0 - And STDOUT should contain: - """ - Current context: admin - """ - - Scenario: Admin context sets $pagenow based on the current command - Given a WP install - And a pagenow-logger.php file: - """ - user_login;"` - Then STDOUT should contain: - """ - Current user: anotheradmin - """ - - Scenario: Admin context resolves an administrator on single site when no user is specified - Given a WP install - When I run `wp --context=admin eval "echo 'User ID: ' . get_current_user_id();"` - Then STDOUT should be: - """ - User ID: 1 - """ - - Scenario: Admin context emits error when no suitable admin user is found on multisite - Given a WP multisite install - And I run `wp eval "update_site_option( 'site_admins', array() );"` - And I try `wp --context=admin eval ''` - Then the return code should be 1 - And STDERR should contain: - """ - Error: No super admin user found. Specify one with --user=. - """ - - Scenario: Admin context emits error when no administrator is found on single site - Given a WP install - When I run `wp user update 1 --role=subscriber` - And I try `wp --context=admin eval ''` - Then the return code should be 1 - And STDERR should contain: - """ - Error: No administrator user found. Specify one with --user=. - """ - - Scenario: Admin context throws an error for a non-existent user - Given a WP install - And a wp-cli.yml file: - """ - user: non_existent_user - """ - And a test.php file: - """ - ` matches an existing site. """ - Scenario: Empty URL - Given a WP installation - - When I try `wp post list --url` - Then STDERR should be: - """ - Warning: The --url parameter expects a value. - """ - - Scenario: Empty URL on multisite - Given a WP multisite installation - - When I try `wp post list --url` - Then STDERR should contain: - """ - Warning: The --url parameter expects a value. - """ - - Scenario: Malformed URL with missing slash in protocol - Given a WP installation - - When I try `wp eval "echo 'done';" --url=http:/example.com` - Then STDERR should be: - """ - Warning: The --url parameter value 'http:/example.com' is not valid. Check for typos in the protocol, e.g. 'http://' not 'http:/'. - """ - And STDOUT should contain: - """ - done - """ - - Scenario: Malformed URL with missing slash in protocol on multisite - Given a WP multisite installation - - When I try `wp eval "echo 'done';" --url=http:/example.com` - Then STDERR should contain: - """ - Warning: The --url parameter value 'http:/example.com' is not valid. Check for typos in the protocol, e.g. 'http://' not 'http:/'. - """ - Scenario: Quiet run Given a WP installation @@ -107,11 +43,10 @@ Feature: Global flags Error: 'non-existing-command' is not a registered wp command. See 'wp help' for available commands. """ - @less-than-php-8 Scenario: Debug run Given a WP installation - When I try `wp eval "echo CONST_WITHOUT_QUOTES;"` + When I try `wp eval 'echo CONST_WITHOUT_QUOTES;'` Then STDOUT should be: """ CONST_WITHOUT_QUOTES @@ -122,7 +57,7 @@ Feature: Global flags """ And the return code should be 0 - When I try `wp eval "echo CONST_WITHOUT_QUOTES;" --debug` + When I try `wp eval 'echo CONST_WITHOUT_QUOTES;' --debug` Then the return code should be 0 And STDOUT should be: """ @@ -136,80 +71,31 @@ Feature: Global flags Scenario: Setting the WP user Given a WP installation - When I run `wp eval "var_export( is_user_logged_in() );"` + When I run `wp eval 'echo (int) is_user_logged_in();'` Then STDOUT should be: """ - false + 0 """ - And STDERR should be empty - When I run `wp --user=admin eval "echo wp_get_current_user()->user_login;"` + When I run `wp --user=admin eval 'echo wp_get_current_user()->user_login;'` Then STDOUT should be: """ admin """ - And STDERR should be empty - When I run `wp --user=admin@example.com eval "echo wp_get_current_user()->user_login;"` + When I run `wp --user=admin@example.com eval 'echo wp_get_current_user()->user_login;'` Then STDOUT should be: """ admin """ - And STDERR should be empty - When I try `wp --user=non-existing-user eval "echo wp_get_current_user()->user_login;"` + When I try `wp --user=non-existing-user eval 'echo wp_get_current_user()->user_login;'` Then the return code should be 1 And STDERR should be: """ Error: Invalid user ID, email or login: 'non-existing-user' """ - Scenario: Warn when provided user is ambiguous - Given a WP installation - - When I run `wp --user=1 eval "echo wp_get_current_user()->user_email;"` - Then STDOUT should be: - """ - admin@example.com - """ - And STDERR should be empty - - When I run `wp user create 1 user1@example.com` - Then STDOUT should contain: - """ - Success: - """ - - When I try `wp --user=1 eval "echo wp_get_current_user()->user_email;"` - Then STDOUT should be: - """ - admin@example.com - """ - And STDERR should be: - """ - Warning: Ambiguous user match detected (both ID and user_login exist for identifier '1'). WP-CLI will default to the ID, but you can force user_login instead with WP_CLI_FORCE_USER_LOGIN=1. - """ - - When I run `WP_CLI_FORCE_USER_LOGIN=1 wp --user=1 eval "echo wp_get_current_user()->user_email;"` - Then STDOUT should be: - """ - user1@example.com - """ - And STDERR should be empty - - When I run `wp --user=user1@example.com eval "echo wp_get_current_user()->user_email;"` - Then STDOUT should be: - """ - user1@example.com - """ - And STDERR should be empty - - When I try `WP_CLI_FORCE_USER_LOGIN=1 wp --user=user1@example.com eval "echo wp_get_current_user()->user_email;"` - Then STDERR should be: - """ - Error: Invalid user login: 'user1@example.com' - """ - Scenario: Using a custom logger Given an empty directory And a custom-logger.php file: @@ -273,7 +159,7 @@ Feature: Global flags require: custom-cmd.php """ - When I run `wp --require=custom-cmd.php test req "This is a custom command."` + When I run `wp --require=custom-cmd.php test req 'This is a custom command.'` Then STDOUT should be: """ foo.php @@ -281,14 +167,12 @@ Feature: Global flags This is a custom command. """ - When I run `WP_CLI_CONFIG_PATH=wp-cli2.yml wp test req "This is a custom command."` + When I run `WP_CLI_CONFIG_PATH=wp-cli2.yml wp test req 'This is a custom command.'` Then STDOUT should contain: """ This is a custom command. """ - # TODO: Fix test for Windows. - @skip-windows Scenario: Using --require with globs Given an empty directory And a foober/foo.php file: @@ -331,7 +215,7 @@ Feature: Global flags """ When I try `wp --color non-existent-command` - Then STDERR should strictly contain: + Then STDERR should contain: """ [31;1mError: """ @@ -391,141 +275,23 @@ Feature: Global flags Error: RESTful WP-CLI needs to be installed. Try 'wp package install wp-cli/restful'. """ - @skip-windows @skip-macos Scenario: Strict args mode should be passed on to ssh - When I try `WP_CLI_STRICT_ARGS_MODE=1 wp --debug --ssh=/ --ssh-args="-o BatchMode=yes" --version` + When I try `WP_CLI_STRICT_ARGS_MODE=1 wp --debug --ssh=/ --version` Then STDERR should contain: """ - Running SSH command: ssh '-o BatchMode=yes' -T -vvv '' 'WP_CLI_STRICT_ARGS_MODE=1 wp + Running SSH command: ssh -q '' -T 'WP_CLI_STRICT_ARGS_MODE=1 wp """ - @skip-windows @skip-macos Scenario: SSH flag should support changing directories When I try `wp --debug --ssh=wordpress:/my/path --version` Then STDERR should contain: """ - Running SSH command: ssh -T -vvv 'wordpress' 'cd '\''/my/path'\''; wp + Running SSH command: ssh -q 'wordpress' -T 'cd '\''/my/path'\''; wp """ - @skip-windows @skip-macos Scenario: SSH flag should support Docker - When I try `WP_CLI_DOCKER_NO_INTERACTIVE=1 wp --debug --ssh=docker:user@wordpress --version` + When I try `wp --debug --ssh=docker:user@wordpress --version` Then STDERR should contain: """ Running SSH command: docker exec --user 'user' 'wordpress' sh -c """ - - @skip-windows @skip-macos - Scenario: SSH args should be passed to SSH command - When I try `wp --debug --ssh=wordpress --ssh-args="-o ConnectTimeout=5" --version` - Then STDERR should contain: - """ - Running SSH command: ssh '-o ConnectTimeout=5' -T -vvv 'wordpress' 'wp - """ - - @skip-windows @skip-macos - Scenario: Multiple SSH args should be passed to SSH command - When I try `wp --debug --ssh=wordpress --ssh-args="-o ConnectTimeout=5" --ssh-args="-o ServerAliveInterval=10" --version` - Then STDERR should contain: - """ - Running SSH command: ssh '-o ConnectTimeout=5' '-o ServerAliveInterval=10' -T -vvv 'wordpress' 'wp - """ - - @skip-windows @skip-macos - Scenario: SSH args should be passed to Docker command - When I try `WP_CLI_DOCKER_NO_INTERACTIVE=1 wp --debug --ssh=docker:wordpress --ssh-args="--env MY_VAR=value" --version` - Then STDERR should contain: - """ - Running SSH command: docker exec '--env MY_VAR=value' 'wordpress' sh -c - """ - - Scenario: Customize config-spec with WP_CLI_CONFIG_SPEC_FILTER_CALLBACK - Given a WP installation - And a wp-cli-early-require.php file: - """ - - """ - - When I run `wp help` - Then STDOUT should contain: - """ - --user= - """ - - Scenario: Double dash delimiter separates options from operands - Given an empty directory - And a test-cmd.php file: - """ - 1, - 'meta_key' => 'foo', - 'meta_value' => 'foo', - ), - (object) array( - 'post_id' => 1, - 'meta_key' => 'fruits', - 'meta_value' => "apple\nbanana\nmango", - ), - (object) array( - 'post_id' => 1, - 'meta_key' => 'bar', - 'meta_value' => 'br', - ), - ); - $assoc_args = array(); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'post_id', 'meta_key', 'meta_value' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should be a table containing rows: - | post_id | meta_key | meta_value | - | 1 | foo | foo | - | 1 | fruits | apple | - | | | banana | - | | | mango | - | 1 | bar | br | - - Scenario: Format boolean values in table and JSON - Given an empty directory - And a file.php file: - """ - 1, - 'status' => true, - ), - array( - 'id' => 2, - 'status' => false, - ), - ); - $assoc_args = array(); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'id', 'status' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should be a table containing rows: - | id | status | - | 1 | true | - | 2 | false | - - Scenario: JSON format preserves boolean types - Given an empty directory - And a file.php file: - """ - 1, - 'status' => true, - ), - array( - 'id' => 2, - 'status' => false, - ), - ); - $assoc_args = array( 'format' => 'json' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'id', 'status' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should be JSON containing: - """ - [{"id":1,"status":true},{"id":2,"status":false}] - """ - - Scenario: Custom fields that exist in some items but not others - Given an empty directory - And a custom-fields.php file: - """ - 'Session 1', - 'custom' => 123, - 'login' => '2018-09-15', - ), - array( - 'name' => 'Session 2', - 'login' => '2018-09-16', - ), - array( - 'name' => 'Session 3', - 'custom' => 456, - 'login' => '2018-09-17', - ), - ); - $assoc_args = array( 'format' => 'table', 'fields' => 'name,custom,login' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name', 'custom', 'login' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file custom-fields.php --skip-wordpress` - Then STDOUT should be a table containing rows: - | name | custom | login | - | Session 1 | 123 | 2018-09-15 | - | Session 2 | | 2018-09-16 | - | Session 3 | 456 | 2018-09-17 | - - Scenario: Custom fields in CSV format with missing values - Given an empty directory - And a custom-fields-csv.php file: - """ - 'Session 1', - 'custom' => 123, - ), - array( - 'name' => 'Session 2', - ), - array( - 'name' => 'Session 3', - 'custom' => 456, - ), - ); - $assoc_args = array( 'format' => 'csv', 'fields' => 'name,custom' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name', 'custom' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file custom-fields-csv.php --skip-wordpress` - Then STDOUT should be CSV containing: - | name | custom | - | Session 1 | 123 | - | Session 2 | | - | Session 3 | 456 | - - Scenario: Custom fields in JSON format with missing values - Given an empty directory - And a custom-fields-json.php file: - """ - 'Session 1', - 'custom' => 123, - ), - array( - 'name' => 'Session 2', - ), - array( - 'name' => 'Session 3', - 'custom' => 456, - ), - ); - $assoc_args = array( 'format' => 'json', 'fields' => 'name,custom' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name', 'custom' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file custom-fields-json.php --skip-wordpress` - Then STDOUT should be JSON containing: - """ - [{"name":"Session 1","custom":123},{"name":"Session 2","custom":null},{"name":"Session 3","custom":456}] - """ - - Scenario: Custom fields in YAML format with missing values - Given an empty directory - And a custom-fields-yaml.php file: - """ - 'Session 1', - 'custom' => 123, - ), - array( - 'name' => 'Session 2', - ), - array( - 'name' => 'Session 3', - 'custom' => 456, - ), - ); - $assoc_args = array( 'format' => 'yaml', 'fields' => 'name,custom' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name', 'custom' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file custom-fields-yaml.php --skip-wordpress` - Then STDOUT should be YAML containing: - """ - --- - - - name: 'Session 1' - custom: 123 - - - name: 'Session 2' - custom: ~ - - - name: 'Session 3' - custom: 456 - """ - - Scenario: Warning when field doesn't exist in any items - Given an empty directory - And a no-field.php file: - """ - 'Session 1', - 'login' => '2018-09-15', - ), - array( - 'name' => 'Session 2', - 'login' => '2018-09-16', - ), - ); - $assoc_args = array( 'format' => 'table', 'fields' => 'name,nonexistent,login' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name', 'nonexistent', 'login' ) ); - $formatter->display_items( $items ); - """ - - When I try `wp eval-file no-field.php --skip-wordpress` - Then STDERR should contain: - """ - Warning: Field not found in any item: nonexistent. - """ - And STDOUT should be a table containing rows: - | name | nonexistent | login | - | Session 1 | | 2018-09-15 | - | Session 2 | | 2018-09-16 | - - Scenario: No warning for missing field with empty list - Given an empty directory - And an empty-list-field.php file: - """ - 'json', 'field' => 'name' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file empty-list-field.php --skip-wordpress` - Then STDOUT should be: - """ - [] - """ - And STDERR should be empty - - Scenario: No warning for missing fields with empty list - Given an empty directory - And an empty-list-fields.php file: - """ - 'json', 'fields' => 'name,login' ); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'name', 'login' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file empty-list-fields.php --skip-wordpress` - Then STDOUT should be: - """ - [] - """ - And STDERR should be empty - - Scenario: Display ordered output for an object item - Given an empty directory - And a file.php file: - """ - 'Custom Name', - 'author' => 'John Doe', - 'version' => '1.0' - ]; - - $assoc_args = [ - 'format' => 'csv', - 'fields' => [ 'version', 'author', 'name' ], - ]; - - $formatter = new WP_CLI\Formatter( $assoc_args ); - $formatter->display_item( $custom_obj ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should contain: - """ - version,1.0 - author,"John Doe" - name,"Custom Name" - """ - - Scenario: Display ordered output for an array item - Given an empty directory - And a file.php file: - """ - 'Custom Name', - 'author' => 'John Doe', - 'version' => '1.0' - ]; - - $assoc_args = [ - 'format' => 'csv', - 'fields' => [ 'version', 'author', 'name' ], - ]; - - $formatter = new WP_CLI\Formatter( $assoc_args ); - $formatter->display_item( $custom_obj ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should contain: - """ - version,1.0 - author,"John Doe" - name,"Custom Name" - """ - - Scenario: Table alignment with right and left aligned columns - Given an empty directory - And a file.php file: - """ - 'A', - 'value' => '100', - ), - array( - 'key' => 'AB', - 'value' => '2000', - ), - array( - 'key' => 'ABC', - 'value' => '30', - ), - ); - // 0 = right, 1 = left - $assoc_args = array( - 'format' => 'table', - 'alignments' => array( 'key' => 0, 'value' => 1 ), - ); - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'key', 'value' ) ); - $formatter->display_items( $items ); - """ - - When I run `SHELL_PIPE=0 wp eval-file file.php --skip-wordpress` - Then STDOUT should strictly be: - """ - +-----+-------+ - | key | value | - +-----+-------+ - | A | 100 | - | AB | 2000 | - | ABC | 30 | - +-----+-------+ - """ - - Scenario: Table alignment with center aligned columns - Given an empty directory - And a file.php file: - """ - 'A', - 'value' => '1', - ), - array( - 'key' => 'ABC', - 'value' => '123', - ), - ); - // 2 = center - $assoc_args = array( - 'format' => 'table', - 'alignments' => array( 'key' => 2, 'value' => 2 ), - ); - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'key', 'value' ) ); - $formatter->display_items( $items ); - """ - - When I run `SHELL_PIPE=0 wp eval-file file.php --skip-wordpress` - Then STDOUT should strictly be: - """ - +-----+-------+ - | key | value | - +-----+-------+ - | A | 1 | - | ABC | 123 | - +-----+-------+ - """ - - Scenario: Table truncates overly large values - Given an empty directory - And a file.php file: - """ - 1, - 'value' => 'short', - ), - (object) array( - 'id' => 2, - 'value' => $large_value, - ), - (object) array( - 'id' => 3, - 'value' => 'another short', - ), - ); - $assoc_args = array(); - $formatter = new WP_CLI\Formatter( $assoc_args, array( 'id', 'value' ) ); - $formatter->display_items( $items ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should contain: - """ - short - """ - And STDOUT should contain: - """ - xxx... - """ - And STDOUT should contain: - """ - another short - """ - And STDOUT should not contain: - """ - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - """ - - Scenario: Format output using prefix without warnings - Given an empty directory - And a file.php file: - """ - 'page', - 'post_name' => 'sample-page', - ), - ); - $assoc_args = array( - 'format' => 'table', - ); - // 'post' prefix should map 'type' to 'post_type' and 'name' to 'post_name' - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'type', 'name' ), 'post' ); - $formatter->display_item( $items[0] ); - """ - - When I run `wp eval-file file.php --skip-wordpress` - Then STDOUT should be a table containing rows: - | Field | Value | - | post_type | page | - | post_name | sample-page | - And STDERR should be empty - - Scenario: Register and use custom format - Given an empty directory - And a custom-format.php file: - """ - \n\n"; - foreach ( $items as $item ) { - echo " \n"; - foreach ( $item as $key => $value ) { - echo " <{$key}>" . htmlspecialchars( $value ) . "\n"; - } - echo " \n"; - } - echo "\n"; - }); - - /** - * Test command with custom format - * - * [--format=] - * : Output format - * --- - * default: table - * options: - * - table - * - json - * - xml - * --- - * - * @when before_wp_load - */ - $test_command = function( $args, $assoc_args ) { - $items = array( - array( 'name' => 'Alice', 'age' => '30' ), - array( 'name' => 'Bob', 'age' => '25' ), - ); - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'name', 'age' ) ); - $formatter->display_items( $items ); - }; - WP_CLI::add_command( 'test-format', $test_command ); - """ - - When I run `wp --require=custom-format.php test-format --format=xml` - Then STDOUT should contain: - """ - - - - Alice - 30 - - - Bob - 25 - - - """ - And the return code should be 0 - - Scenario: Filter available formats - Given a WP installation - And a filter-formats.php file: - """ - 'Test' ), - ); - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'name' ) ); - $formatter->display_items( $items ); - }; - WP_CLI::add_command( 'test-invalid', $test_command ); - """ - - When I try `wp --require=invalid-format.php test-invalid --format=nonexistent` - Then STDERR should contain: - """ - Invalid format: nonexistent - """ - And the return code should be 1 - - Scenario: Single item display with unsupported built-in formats (count/ids) - Given an empty directory - And a test-single-item.php file: - """ - 'Alice' ); - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'name' ) ); - $formatter->display_item( $item ); - }; - WP_CLI::add_command( 'test-single-item', $test_command ); - """ - - When I try `wp --require=test-single-item.php test-single-item --format=ids` - Then STDERR should contain: - """ - Error: Invalid format: ids - """ - And the return code should be 1 - - When I try `wp --require=test-single-item.php test-single-item --format=count` - Then STDERR should contain: - """ - Error: Invalid format: count - """ - And the return code should be 1 - - Scenario: Single item display with custom format opting out via options - Given an empty directory - And a test-custom-optout.php file: - """ - false ) ); - - /** - * Test command - * - * @when before_wp_load - */ - $test_command = function( $args, $assoc_args ) { - $item = array( 'name' => 'Bob' ); - $formatter = new \WP_CLI\Formatter( $assoc_args, array( 'name' ) ); - $formatter->display_item( $item ); - }; - WP_CLI::add_command( 'test-optout', $test_command ); - """ - - When I try `wp --require=test-custom-optout.php test-optout --format=no_single` - Then STDERR should contain: - """ - Error: Invalid format: no_single - """ - And the return code should be 1 - - Scenario: Single value printing with plaintext alias - Given an empty directory - And a test-plaintext.php file: - """ - 'value' ); - WP_CLI::print_value( $value, $assoc_args ); - }; - WP_CLI::add_command( 'test-plaintext', $test_command ); - """ - - When I run `wp --require=test-plaintext.php test-plaintext --format=plaintext` - Then STDOUT should contain: - """ - 'nested' => 'value' - """ - And the return code should be 0 - diff --git a/features/framework.feature b/features/framework.feature index fbd0b57b7..9908eb71e 100644 --- a/features/framework.feature +++ b/features/framework.feature @@ -81,12 +81,9 @@ Feature: Load WP-CLI """ And STDERR should contain: """ - Error: This does not seem to be a WordPress installation. + Error: This does not seem to be a WordPress install. """ - # `wp db create` does not yet work on SQLite, - # See https://github.com/wp-cli/db-command/issues/234 - @require-mysql Scenario: Globalize global variables in wp-config.php Given an empty directory And WP files @@ -95,7 +92,7 @@ Feature: Load WP-CLI $redis_server = 'foo'; """ - When I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check --extra-php < wp-config-extra.php` + When I run `wp core config {CORE_CONFIG_SETTINGS} --extra-php < wp-config-extra.php` Then the wp-config.php file should contain: """ $redis_server = 'foo'; @@ -105,13 +102,7 @@ Feature: Load WP-CLI And I run `wp core install --url='localhost:8001' --title='Test' --admin_user=wpcli --admin_email=admin@example.com --admin_password=1` Then STDOUT should not be empty - And a eval-redis-server.php file: - """ - ` to override. """ - # Undo above. - Given I run `wp db query 'UPDATE wp_blogs SET domain = "example.com"'` - When I try `wp option get home --url=example.io` Then STDERR should be: """ Error: Site 'example.io' not found. Verify `--url=` matches an existing site. """ - # `wp db query` does not yet work on SQLite, - # See https://github.com/wp-cli/db-command/issues/234 - @require-mysql - Scenario: Show detailed error when multisite network is not found - Given a WP multisite installation - And a force-network-not-found.php file: - """ - ' to get more information on a specific command. """ - And STDOUT should contain: - """ - https://make.wordpress.org/cli/handbook/ - """ And STDERR should be empty When I run `wp help core` @@ -20,10 +16,6 @@ Feature: Get help about WP-CLI commands """ wp core """ - And STDOUT should not contain: - """ - https://make.wordpress.org/cli/handbook/ - """ And STDERR should be empty When I run `wp help core download` @@ -54,124 +46,47 @@ Feature: Get help about WP-CLI commands """ And STDERR should be empty - When I run `wp post list --post_type=post --posts_per_page=5 --help --prompt` - Then STDOUT should contain: - """ - wp post list - """ - And STDERR should be empty - - Scenario: Include when the command is run if a non-standard hook. - Given an empty directory - - When I run `COLUMNS=80 wp help db` - Then STDOUT should contain: - """ - Unless overridden, these commands run on the 'after_wp_config_load' hook, - after wp-config.php has been loaded into scope. - """ - - When I run `COLUMNS=150 wp help db check` - Then STDOUT should contain: - """ - This command runs on the 'after_wp_config_load' hook, after wp-config.php has been loaded into scope. - """ - - When I run `COLUMNS=150 wp help db size` - Then STDOUT should not contain: - """ - This command runs on the - """ - - Scenario: Hide Global parameters when requested - Given an empty directory - - When I run `wp help` - Then STDOUT should contain: - """ - GLOBAL PARAMETERS - """ - - And STDOUT should contain: - """ - --path - """ - - And STDOUT should contain: - """ - Path to the WordPress files. - """ - - When I run `WP_CLI_SUPPRESS_GLOBAL_PARAMS=true wp help` - Then STDOUT should not contain: - """ - GLOBAL PARAMETERS - """ - - And STDOUT should not contain: - """ - --path - """ - And STDOUT should not contain: - """ - Path to the WordPress files. - """ - - When I run `WP_CLI_SUPPRESS_GLOBAL_PARAMS=false wp help` - Then STDOUT should contain: - """ - GLOBAL PARAMETERS - """ - - And STDOUT should contain: - """ - --path - """ - And STDOUT should contain: - """ - Path to the WordPress files. - """ - + # Prior to WP 4.3 widgets & others used PHP 4 style constructors and prior to WP 3.9 wpdb used the mysql extension which can all lead (depending on PHP version) to PHP Deprecated notices. + @require-wp-4.3 Scenario: Help for internal commands with WP Given a WP installation - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help` + When I run `wp help` Then STDOUT should contain: """ Run 'wp help ' to get more information on a specific command. """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help core` + When I run `wp help core` Then STDOUT should contain: """ wp core """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help core download` + When I run `wp help core download` Then STDOUT should contain: """ wp core download """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help help` + When I run `wp help help` Then STDOUT should contain: """ wp help """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help help` + When I run `wp help help` Then STDOUT should contain: """ GLOBAL PARAMETERS """ + And STDERR should be empty - @require-php-7.4 Scenario: Help when WordPress is downloaded but not installed Given an empty directory @@ -183,7 +98,7 @@ Feature: Get help about WP-CLI commands """ And STDERR should be empty - When I run `wp config create {CORE_CONFIG_SETTINGS} --skip-check` + When I run `wp config create {CORE_CONFIG_SETTINGS}` And I run `wp help core install` Then STDOUT should contain: """ @@ -198,36 +113,47 @@ Feature: Get help about WP-CLI commands """ And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help core` + When I run `wp help core` Then STDOUT should contain: """ wp core """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help config` + When I run `wp help config` Then STDOUT should contain: """ wp config """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help db` + When I run `wp help db` Then STDOUT should contain: """ wp db """ + And STDERR should be empty When I try `wp help non-existent-command` Then the return code should be 1 - And STDERR should contain: + And STDERR should be: """ - Error establishing a database connection + Warning: Can’t select database. We were able to connect to the database server (which means your username and password is okay) but not able to select the `wp_cli_test` database. + Error: 'non-existent-command' is not a registered wp command. See 'wp help' for available commands. """ - And STDERR should contain: + And STDOUT should be empty + + # Bug: if `WP_DEBUG` (or `WP_DEBUG_DISPLAY') is defined falsey than a db error won't be trapped. + Given a define-wp-debug-false.php file: """ - Error: 'non-existent-command' is not a registered wp command. See 'wp help' for available commands. + + Warning: No WordPress install found. If the command 'confi' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'confi' is not a registered wp command. See 'wp help' for available commands. + Did you mean 'config'? """ - And the return code should be 0 + And STDOUT should be empty - When I try `wp help cor < session_yes` - Then STDERR should contain: - """ - Warning: 'cor' is not a registered wp command. See 'wp help' for available commands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'core'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: + When I try `wp help cor` + Then the return code should be 1 + And STDERR should be: """ - wp core + Warning: No WordPress install found. If the command 'cor' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'cor' is not a registered wp command. See 'wp help' for available commands. + Did you mean 'core'? """ - And the return code should be 0 + And STDOUT should be empty - When I try `wp help d < session_yes` - Then STDERR should contain: - """ - Warning: 'd' is not a registered wp command. See 'wp help' for available commands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'db'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: + When I try `wp help d` + Then the return code should be 1 + And STDERR should be: """ - wp db + Warning: No WordPress install found. If the command 'd' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'd' is not a registered wp command. See 'wp help' for available commands. + Did you mean 'db'? """ - And the return code should be 0 + And STDOUT should be empty - When I try `wp help packag < session_yes` - Then STDERR should contain: - """ - Warning: 'packag' is not a registered wp command. See 'wp help' for available commands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'package'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: + When I try `wp help packag` + Then the return code should be 1 + And STDERR should be: """ - wp package + Warning: No WordPress install found. If the command 'packag' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'packag' is not a registered wp command. See 'wp help' for available commands. + Did you mean 'package'? """ - And the return code should be 0 + And STDOUT should be empty Scenario: Suggestions for subcommand typos in help of specially treated commands Given an empty directory - And a session_no file: - """ - n - """ - And a session_yes file: - """ - y - """ - - When I try `wp help config creat < session_no` - Then STDERR should contain: - """ - Warning: 'creat' is not a registered subcommand of 'config'. See 'wp help config' for available subcommands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'create'? [y/n] - """ - And STDOUT should not contain: - """ - SYNOPSIS - """ - And the return code should be 0 - When I try `wp help config creat < session_yes` - Then STDERR should contain: - """ - Warning: 'creat' is not a registered subcommand of 'config'. See 'wp help config' for available subcommands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'create'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: - """ - wp config create - """ - And the return code should be 0 - - When I try `wp help core versio < session_yes` - Then STDERR should contain: - """ - Warning: 'versio' is not a registered subcommand of 'core'. See 'wp help core' for available subcommands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'version'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: - """ - wp core version - """ - And the return code should be 0 - - When I try `wp help core versio < session_yes` - Then STDERR should contain: - """ - Warning: 'versio' is not a registered subcommand of 'core'. See 'wp help core' for available subcommands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'version'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: - """ - wp core version - """ - And the return code should be 0 - - When I try `wp help db chec < session_yes` - Then STDERR should contain: - """ - Warning: 'chec' is not a registered subcommand of 'db'. See 'wp help db' for available subcommands. - """ - And STDERR should not contain: - """ - No WordPress installation found. - """ - And STDOUT should contain: - """ - Did you mean 'check'? [y/n] - """ - And STDOUT should contain: - """ - SYNOPSIS - """ - And STDOUT should contain: + When I try `wp help config creat` + Then the return code should be 1 + And STDERR should be: """ - wp db check + Warning: No WordPress install found. If the command 'config creat' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'creat' is not a registered subcommand of 'config'. See 'wp help config' for available subcommands. + Did you mean 'create'? """ - And the return code should be 0 + And STDOUT should be empty - Scenario: Suggestions for chained command typos in help - Given a WP installation - And a session_yes_yes file: + When I try `wp help core versio` + Then the return code should be 1 + And STDERR should be: """ - y - y + Warning: No WordPress install found. If the command 'core versio' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'versio' is not a registered subcommand of 'core'. See 'wp help core' for available subcommands. + Did you mean 'version'? """ + And STDOUT should be empty - When I try `wp hel post seta < session_yes_yes` - Then STDERR should contain: - """ - Warning: 'hel' is not a registered wp command. See 'wp help' for available commands. - """ - And STDOUT should contain: - """ - Did you mean 'help'? [y/n] - """ - And STDERR should contain: - """ - Warning: 'seta' is not a registered subcommand of 'post'. See 'wp help post' for available subcommands. - """ - And STDOUT should contain: - """ - Did you mean 'meta'? [y/n] - """ - And STDOUT should contain: + When I try `wp help db chec` + Then the return code should be 1 + And STDERR should be: """ - wp post meta + Warning: No WordPress install found. If the command 'db chec' is in a plugin or theme, pass --path=`path/to/wordpress`. + Error: 'chec' is not a registered subcommand of 'db'. See 'wp help db' for available subcommands. + Did you mean 'check'? """ - And the return code should be 0 + And STDOUT should be empty - Scenario: No WordPress installation warning or suggestions for disabled commands + Scenario: No WordPress install warning or suggestions for disabled commands Given an empty directory And a wp-cli.yml file: """ @@ -633,39 +360,6 @@ Feature: Get help about WP-CLI commands """ And STDOUT should be empty - Scenario: Disabled commands show up in help with reason - Given an empty directory - And a disable-command.php file: - """ - abort( 'This command is for testing only.' ); - } ); - /** - * A test command. - */ - WP_CLI::add_command( 'test-disabled', function() {} ); - """ - - When I try `wp help --require=disable-command.php` - Then STDOUT should contain: - """ - test-disabled - """ - And STDOUT should contain: - """ - A test command. - """ - And STDOUT should contain: - """ - This command is for testing only. - """ - And STDERR should contain: - """ - Warning: Aborting the addition of the command 'test-disabled' with reason: This command is for testing only.. - """ - - Scenario: Help for third-party commands Given a WP installation And a wp-content/plugins/test-cli/command.php file: @@ -684,19 +378,19 @@ Feature: Get help about WP-CLI commands """ And I run `wp plugin activate test-cli` - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help` + When I run `wp help` Then STDOUT should contain: """ A dummy command. """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help test-help` + When I run `wp help test-help` Then STDOUT should contain: """ wp test-help """ + And STDERR should be empty Scenario: Help for incomplete commands Given an empty directory @@ -735,7 +429,7 @@ Feature: Get help about WP-CLI commands function __sleep() {} function __wakeup() {} function __toString() {} - static function __set_state( $properties ) {} + function __set_state() {} function __clone() {} function __debugInfo() {} } @@ -776,8 +470,7 @@ Feature: Get help about WP-CLI commands """ And I run `wp plugin activate test-cli` - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help site` + When I run `wp help site` Then STDOUT should contain: """ test-extra @@ -804,22 +497,19 @@ Feature: Get help about WP-CLI commands WP_CLI::add_command( 'db test-extra-db', 'Test_CLI_Extra_Command' ); """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help config` + When I run `wp help config` Then STDOUT should contain: """ test-extra-config """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help core` + When I run `wp help core` Then STDOUT should contain: """ test-extra-core """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help db` + When I run `wp help db` Then STDOUT should contain: """ test-extra-db @@ -828,8 +518,7 @@ Feature: Get help about WP-CLI commands Scenario: Help renders global parameters correctly Given a WP installation - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help core` + When I run `wp help import get` Then STDOUT should contain: """ GLOBAL PARAMETERS @@ -839,8 +528,7 @@ Feature: Get help about WP-CLI commands ## GLOBAL PARAMETERS """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help option get` + When I run `wp help option get` Then STDOUT should contain: """ GLOBAL PARAMETERS @@ -850,8 +538,7 @@ Feature: Get help about WP-CLI commands ## GLOBAL PARAMETERS """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `wp help option` + When I run `wp help option` Then STDOUT should contain: """ GLOBAL PARAMETERS @@ -878,8 +565,6 @@ Feature: Get help about WP-CLI commands ALIAS """ - # No vt100 on Windows. - @skip-windows Scenario: Help for commands should wordwrap well Given a WP installation And a wp-content/plugins/test-cli/command.php file: @@ -935,8 +620,7 @@ Feature: Get help about WP-CLI commands """ And I run `wp plugin activate test-cli` - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `COLUMNS=80 wp help test-wordwrap my_command` + When I run `COLUMNS=80 wp help test-wordwrap my_command` Then STDOUT should contain: """ 123456789 123456789 123456789 123456789 123456789 123456789 123456789 12345678 @@ -991,16 +675,15 @@ Feature: Get help about WP-CLI commands the target site is specified. """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `COLUMNS=80 wp help test-wordwrap my_command | awk '{print length, $0}' | sort -nr | head -1 | cut -f1 -d" "` + When I run `COLUMNS=80 wp help test-wordwrap my_command | awk '{print length, $0}' | sort -nr | head -1 | cut -f1 -d" "` Then STDOUT should be: """ 80 """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=40 wp help test-wordwrap my_command` + When I run `TERM=vt100 COLUMNS=40 wp help test-wordwrap my_command` Then STDOUT should contain: """ 123456789 123456789 123456789 @@ -1068,16 +751,15 @@ Feature: Get help about WP-CLI commands specified. """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=40 wp help test-wordwrap my_command | sed '/\-\-ssh/d' | awk '{print length, $0}' | sort -nr | head -1 | cut -f1 -d" "` + When I run `TERM=vt100 COLUMNS=40 wp help test-wordwrap my_command | sed '/\-\-ssh/d' | awk '{print length, $0}' | sort -nr | head -1 | cut -f1 -d" "` Then STDOUT should be: """ 40 """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=1000 wp help test-wordwrap my_command` + When I run `TERM=vt100 COLUMNS=1000 wp help test-wordwrap my_command` Then STDOUT should contain: """ [--skip-delete] @@ -1102,9 +784,8 @@ Feature: Get help about WP-CLI commands Pretend request came from given URL. In multisite, this argument is how the target site is specified. """ + And STDERR should be empty - # No vt100 on Windows. - @skip-windows Scenario: Help for commands with subcommands should wordwrap well Given a WP installation And a wp-content/plugins/test-cli/command.php file: @@ -1185,8 +866,7 @@ Feature: Get help about WP-CLI commands """ And I run `wp plugin activate test-cli` - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=80 wp help test-wordwrap` + When I run `TERM=vt100 COLUMNS=80 wp help test-wordwrap` Then STDOUT should contain: """ SUBCOMMANDS @@ -1210,16 +890,14 @@ Feature: Get help about WP-CLI commands longgg description. """ + And STDERR should be empty - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=80 wp help test-wordwrap | awk '{print length, $0}' | sort -nr | head -1 | cut -f1 -d" "` + When I run `TERM=vt100 COLUMNS=80 wp help test-wordwrap | awk '{print length, $0}' | sort -nr | head -1 | cut -f1 -d" "` Then STDOUT should be: """ 80 """ - # No vt100 on Windows. - @skip-windows Scenario: Long description for top-level command which has reference link display well Given a WP installation And a command.php file: @@ -1235,7 +913,7 @@ Feature: Get help about WP-CLI commands * A command that has a link in its long description. * * This is a [reference link](https://wordpress.org/). - * Also, there is a [second link](http://example.com/). + * Also, there is a [second link](http://wp-cli.org/). * They should be displayed nicely! * * @synopsis @@ -1251,8 +929,7 @@ Feature: Get help about WP-CLI commands - command.php """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=80 wp help reference-link` + When I run `TERM=vt100 COLUMNS=80 wp help reference-link` Then STDOUT should contain: """ This is a [reference link][1]. @@ -1261,11 +938,19 @@ Feature: Get help about WP-CLI commands --- [1] https://wordpress.org/ - [2] http://example.com/ + [2] http://wp-cli.org/ + """ + + When I run `wp help role` + Then STDOUT should contain: + """ + See references for [Roles and Capabilities][1] and [WP User class][2]. + + --- + [1] https://codex.wordpress.org/Roles_and_Capabilities + [2] https://codex.wordpress.org/Class_Reference/WP_User """ - # No vt100 on Windows. - @skip-windows Scenario: Very long description for top-level command which has reference link display well Given a WP installation And a command.php file: @@ -1280,7 +965,7 @@ Feature: Get help about WP-CLI commands /** * A command that has a link in its long description. * - * This is a [reference link](https://wordpress.org/). Also, there is a [second link](http://example.com/). They should be displayed nicely! Wow! This is a very, very long description. + * This is a [reference link](https://wordpress.org/). Also, there is a [second link](http://wp-cli.org/). They should be displayed nicely! Wow! This is a very, very long description. * * @synopsis */ @@ -1295,8 +980,7 @@ Feature: Get help about WP-CLI commands - command.php """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=80 wp help reference-link` + When I run `TERM=vt100 COLUMNS=80 wp help reference-link` Then STDOUT should contain: """ This is a [reference link][1]. Also, there is a [second link][2]. They should @@ -1304,11 +988,10 @@ Feature: Get help about WP-CLI commands --- [1] https://wordpress.org/ - [2] http://example.com/ + [2] http://wp-cli.org/ """ - # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 - When I try `TERM=vt100 COLUMNS=60 wp help reference-link` + When I run `TERM=vt100 COLUMNS=60 wp help reference-link` Then STDOUT should contain: """ This is a [reference link][1]. Also, there is a [second @@ -1317,10 +1000,9 @@ Feature: Get help about WP-CLI commands --- [1] https://wordpress.org/ - [2] http://example.com/ + [2] http://wp-cli.org/ """ - @skip-windows Scenario Outline: Check that proc_open() and proc_close() aren't disabled for help pager Given an empty directory When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=} help --debug` @@ -1328,179 +1010,9 @@ Feature: Get help about WP-CLI commands """ Warning: check_proc_available() failed in pass_through_pager(). """ - And STDOUT should not be empty And the return code should be 0 Examples: | func | | proc_open | | proc_close | - - Scenario: Multi-paragraph description appears in DESCRIPTION section - Given a WP installation - And a wp-content/plugins/test-cli/command.php file: - """ - - * : Cloudflare zone ID. - * - * ## EXAMPLES - * - * # Clear the cloudflare cache for a domain. - * wp test-multiline clear-cloudflare-cache 12345 - * Success: The Cloudflare cache has been cleared. - * - * @subcommand clear-cloudflare-cache - * @alias clear_cloudflare_cache - */ - public function clear_cloudflare_cache( $args ) {} - } - - WP_CLI::add_command( 'test-multiline', 'Test_Multiline_Description' ); - """ - And I run `wp plugin activate test-cli` - - When I run `COLUMNS=80 wp help test-multiline clear-cloudflare-cache` - Then STDOUT should contain: - """ - DESCRIPTION - - Clear the Cloudflare cache. Default: purge everything. Uses - $CLOUDFLARE_API_KEY environment variable. - - API documentation: https://developers.cloudflare.com/api/resources/cache/ - - SYNOPSIS - """ - And STDOUT should contain: - """ - ALIAS - - clear_cloudflare_cache - - OPTIONS - """ - - Scenario: Multi-paragraph description appears in DESCRIPTION section without alias - Given a WP installation - And a wp-content/plugins/test-cli/command.php file: - """ - - * : Cloudflare zone ID. - * - * ## EXAMPLES - * - * # Clear the cloudflare cache for a domain. - * wp test-multiline noalias 12345 - * Success: The Cloudflare cache has been cleared. - */ - public function noalias( $args ) {} - } - - WP_CLI::add_command( 'test-multiline', 'Test_Multiline_No_Alias' ); - """ - And I run `wp plugin activate test-cli` - - When I run `COLUMNS=80 wp help test-multiline noalias` - Then STDOUT should contain: - """ - DESCRIPTION - - Clear the Cloudflare cache. Default: purge everything. Uses - $CLOUDFLARE_API_KEY environment variable. - - API documentation: https://developers.cloudflare.com/api/resources/cache/ - - SYNOPSIS - """ - And STDOUT should not contain: - """ - ALIAS - """ - And STDOUT should contain: - """ - OPTIONS - - - """ - - @skip-windows - Scenario: Pager without color support should not show ANSI escape codes - Given an empty directory - - When I run `PAGER=cat wp help | head -1` - Then STDOUT should not match /\x1b\[/ - And STDOUT should not match /\033\[/ - - When I run `PAGER=more wp help | head -1` - Then STDOUT should not match /\x1b\[/ - And STDOUT should not match /\033\[/ - - When I run `PAGER=/usr/bin/more wp help | head -1` - Then STDOUT should not match /\x1b\[/ - And STDOUT should not match /\033\[/ - - When I run `PAGER=/bin/cat wp help | head -1` - Then STDOUT should not match /\x1b\[/ - And STDOUT should not match /\033\[/ - - When I run `PAGER=less wp help | head -1` - Then STDOUT should not match /\x1b\[/ - And STDOUT should not match /\033\[/ - - Scenario: Disabled commands are shown in help listings - Given an empty directory - And a wp-cli.yml file: - """ - disabled_commands: - - core - """ - - When I run `wp help` - Then STDOUT should contain: - """ - core - """ - And STDOUT should contain: - """ - Disabled via configuration file - """ - - Scenario: Full help shows all subcommands recursively - Given an empty directory - - When I run `wp help core --full` - Then STDOUT should contain: - """ - wp core - """ - And STDOUT should contain: - """ - wp core check-update - """ - And STDOUT should contain: - """ - wp core download - """ - And STDERR should be empty diff --git a/features/hook.feature b/features/hook.feature index 4483b5959..f94191381 100644 --- a/features/hook.feature +++ b/features/hook.feature @@ -1,6 +1,6 @@ Feature: Tests `WP_CLI::add_hook()` - Scenario: Add callback to the `before_invoke:plugin list` + Scenario: Add callback to the `before_invoke` Given a WP installation And a before-invoke.php file: """ @@ -24,10 +24,7 @@ Feature: Tests `WP_CLI::add_hook()` """ And the return code should be 0 - # `wp db check` does not yet work on SQLite, - # See https://github.com/wp-cli/db-command/issues/234 - @require-mysql - Scenario: Add callback to the `before_invoke:db check` + Scenario: Add callback to the `before_invoke` Given a WP installation And a before-invoke.php file: """ @@ -51,7 +48,7 @@ Feature: Tests `WP_CLI::add_hook()` """ And the return code should be 0 - Scenario: Add callback to the `before_invoke:core version` + Scenario: Add callback to the `before_invoke` Given a WP installation And a before-invoke.php file: """ @@ -74,150 +71,3 @@ Feature: Tests `WP_CLI::add_hook()` `add_hook()` to the `before_invoke` is working. """ And the return code should be 0 - - Scenario: Add callback to the `before_run_command` with args - Given a WP installation - And a before-run-command.php file: - """ - 5 ] ); - WP_CLI::success( 'HTTP request completed' ); - } catch ( Exception $e ) { - WP_CLI::error( 'HTTP request failed: ' . $e->getMessage() ); - } - }); - """ - And a wp-cli.yml file: - """ - require: - - http-test.php - """ - - When I try `wp http-test --debug=http` - Then STDERR should contain: - """ - Debug: HTTP GET request to https://api.wordpress.org/core/version-check/1.7/ - """ - And the return code should be 0 - - Scenario: HTTP requests are not logged without debug flag - Given a WP installation - And a http-test.php file: - """ - 5 ] ); - WP_CLI::success( 'HTTP request completed' ); - } catch ( Exception $e ) { - WP_CLI::error( 'HTTP request failed: ' . $e->getMessage() ); - } - }); - """ - And a wp-cli.yml file: - """ - require: - - http-test.php - """ - - When I run `wp http-test` - Then STDERR should not contain: - """ - HTTP GET request to - """ - And the return code should be 0 - - Scenario: Different HTTP methods are logged correctly - Given a WP installation - And a http-methods-test.php file: - """ - 5 ] ); - } catch ( Exception $e ) { - // Ignore errors for this test - } - - // POST request - try { - WP_CLI\Utils\http_request( 'POST', $test_url . 'post', ['test' => 'data'], [], [ 'timeout' => 5 ] ); - } catch ( Exception $e ) { - // Ignore errors for this test - } - - WP_CLI::success( 'Test completed' ); - }); - """ - And a wp-cli.yml file: - """ - require: - - http-methods-test.php - """ - - When I try `wp http-methods-test --debug=http` - Then STDERR should contain: - """ - Debug: HTTP GET request to https://httpbin.org/get - """ - And STDERR should contain: - """ - Debug: HTTP POST request to https://httpbin.org/post - """ - And the return code should be 0 diff --git a/features/launch-env-forwarding.feature b/features/launch-env-forwarding.feature deleted file mode 100644 index 5cda3f4f6..000000000 --- a/features/launch-env-forwarding.feature +++ /dev/null @@ -1,52 +0,0 @@ -Feature: Environment variables are forwarded to spawned processes - - In order to avoid configuration and database connection issues - As a WP-CLI user - I want WP_CLI::launch() to forward environment variables to child processes - - Background: - Given an empty directory - And an env-dump.php file: - """ - stdout; - """ - - # Case 1: Normal PHP configuration where `variables_order` includes "E". - # In this case, the parent shell sets WPCLI_ENV_FWD=ok before launching - # WP-CLI. WP_CLI::launch() should forward the environment so that the - # child "php env-dump.php" process can read WPCLI_ENV_FWD via getenv(). - # - # We use the detailed ProcessRun result and explicitly echo $result->stdout - # so that Behat can assert on the output from the spawned process. - Scenario: Forwards environment variables when $_ENV is populated - When I run `WPCLI_ENV_FWD=ok wp --allow-root --skip-wordpress eval-file run-env-check.php` - Then STDOUT should contain: - """ - ok - """ - - # Case 2: PHP is started with variables_order=GPCS, so "E" is omitted. - # This means $_ENV may be empty in the parent process, but the OS-level - # environment still contains WPCLI_ENV_FWD=ok. When WP_CLI::launch() - # falls back to passing "null" as the env array, the child process should - # inherit that environment and still see WPCLI_ENV_FWD=ok. - # - # Again, we echo $result->stdout from inside the eval so Behat can assert - # that the spawned process received the forwarded environment variable. - Scenario: Still forwards env vars when $_ENV is empty - When I run `WPCLI_ENV_FWD=ok WP_CLI_PHP_ARGS="-d variables_order=GPCS" wp --allow-root --skip-wordpress eval-file run-env-check.php` - Then STDOUT should contain: - """ - ok - """ diff --git a/features/make-phar.feature b/features/make-phar.feature new file mode 100644 index 000000000..14799793e --- /dev/null +++ b/features/make-phar.feature @@ -0,0 +1,28 @@ +Feature: Check `utils/make-phar.php` output + + Scenario: Check autoload stripping of phpcs development classes + Given an empty directory + And a new Phar with the same version + And a custom-cmd.php file: + """ + ...] - * : Filter by status - * --- - * options: - * - active - * - inactive - * - pending - * --- - * - * @subcommand list - */ - public function list_( $args, $assoc_args ) { - if ( isset( $assoc_args['status'] ) ) { - if ( is_array( $assoc_args['status'] ) ) { - WP_CLI::success( 'Status filter: ' . implode( ', ', $assoc_args['status'] ) ); - } else { - WP_CLI::success( 'Status filter: ' . $assoc_args['status'] ); - } - } else { - WP_CLI::success( 'No status filter' ); - } - } - } - WP_CLI::add_command( 'testmulti', 'Test_Multi_Command' ); - """ - - When I run `wp --require=test-cmd.php testmulti list` - Then STDOUT should contain: - """ - Success: No status filter - """ - - When I run `wp --require=test-cmd.php testmulti list --status=active` - Then STDOUT should contain: - """ - Success: Status filter: active - """ - - When I run `wp --require=test-cmd.php testmulti list --status=active --status=inactive` - Then STDOUT should contain: - """ - Success: Status filter: active, inactive - """ - - When I run `wp --require=test-cmd.php testmulti list --status=active --status=inactive --status=pending` - Then STDOUT should contain: - """ - Success: Status filter: active, inactive, pending - """ - - Scenario: Command without repeating parameter uses last value when flag is repeated - Given an empty directory - And a test-single-cmd.php file: - """ - ] - * : Filter by status - * --- - * options: - * - active - * - inactive - * - pending - * --- - * - * @subcommand list - */ - public function list_( $args, $assoc_args ) { - if ( isset( $assoc_args['status'] ) ) { - WP_CLI::success( 'Status filter: ' . $assoc_args['status'] ); - } else { - WP_CLI::success( 'No status filter' ); - } - } - } - WP_CLI::add_command( 'testsingle', 'Test_Single_Command' ); - """ - - When I run `wp --require=test-single-cmd.php testsingle list --status=active --status=inactive` - Then STDOUT should contain: - """ - Success: Status filter: inactive - """ - - Scenario: Multiple flag values with option validation - Given an empty directory - And a test-validation-cmd.php file: - """ - ...] - * : Filter by status - * --- - * options: - * - active - * - inactive - * --- - * - * @subcommand list - */ - public function list_( $args, $assoc_args ) { - if ( isset( $assoc_args['status'] ) ) { - if ( is_array( $assoc_args['status'] ) ) { - WP_CLI::success( 'Filters: ' . implode( ', ', $assoc_args['status'] ) ); - } else { - WP_CLI::success( 'Filters: ' . $assoc_args['status'] ); - } - } - } - } - WP_CLI::add_command( 'testval', 'Test_Validation_Command' ); - """ - - When I run `wp --require=test-validation-cmd.php testval list --status=active --status=inactive` - Then STDOUT should contain: - """ - Success: Filters: active, inactive - """ - - When I try `wp --require=test-validation-cmd.php testval list --status=active --status=invalid` - Then the return code should be 1 - And STDERR should contain: - """ - Invalid value 'invalid' specified for 'status' - """ - - When I try `wp --require=test-validation-cmd.php testval list --status=invalid --status=active` - Then the return code should be 1 - And STDERR should contain: - """ - Invalid value 'invalid' specified for 'status' - """ - - Scenario: Boolean flags use last-wins behavior when repeated - Given an empty directory - And a test-boolean-cmd.php file: - """ - - * : A positional arg. - * - * - * : A positional arg. - * - * [--flag1=] - * : A flag. - * - * [--flag2=] - * : A flag. - * - * [--flag3=] - * : A flag. - * - * @when before_wp_load - */ - WP_CLI::add_command( 'test-prompt', function( $args, $assoc_args ) { - WP_CLI::line( 'arg1: ' . $args[0] ); - WP_CLI::line( 'arg2: ' . $args[1] ); - WP_CLI::line( 'flag1: ' . $assoc_args['flag1'] ); - WP_CLI::line( 'flag2: ' . $assoc_args['flag2'] ); - WP_CLI::line( 'flag3: ' . $assoc_args['flag3'] ); - } ); - """ - And a value-file file: - """ - positional2 - value2 - """ - And a wp-cli.yml file: - """ - require: - - cmd.php - """ - - When I run `wp test-prompt positional1 --flag1=value1 --flag3=value3 --prompt < value-file` - Then the return code should be 0 - And STDERR should be empty - And STDOUT should contain: - """ - arg1: positional1 - """ - And STDOUT should contain: - """ - arg2: positional2 - """ - And STDOUT should contain: - """ - flag1: value1 - """ - And STDOUT should contain: - """ - flag2: value2 - """ - And STDOUT should contain: - """ - flag3: value3 - """ - - Scenario: Prompt should show full command after inputs - Given a WP installation - And a value-file file: - """ - post_type - post - - - post_title,post_name,post_status - csv - """ - When I run `wp post create --post_title="Publish post" --post_content="Publish post content" --post_status="publish"` - Then STDOUT should not be empty - - When I run `wp post create --post_title="Publish post 2" --post_content="Publish post content" --post_status="publish"` - Then STDOUT should not be empty - - When I run `wp post list --prompt < value-file` - Then STDOUT should match #wp post list --post_type='post' --fields='post_title,post_name,post_status' --format='csv'|wp post list --post_type="post" --fields="post_title,post_name,post_status" --format="csv"# - And STDOUT should contain: - """ - post_title,post_name,post_status - """ - And STDOUT should contain: - """ - "Publish post 2",publish-post-2,publish - """ - And STDOUT should contain: - """ - "Publish post",publish-post,publish - """ - And STDOUT should contain: - """ - "Hello world!",hello-world,publish - """ - - Scenario: Prompt should show positional arguments - Given a WP installation - And a value-file file: - """ - category - General - general - - - - """ - - When I run `wp term create --prompt < value-file` - Then STDOUT should match #wp term create 'category' 'General' --slug='general'|wp term create "category" "General" --slug="general"# - And STDOUT should contain: - """ - Created category - """ - - # Skip on Windows due to output differences. PowerShell masks the `--password` argument name. - # TODO: Investigate. - @skip-windows - Scenario: Prompt should mask sensitive argument values - Given an empty directory - And a cmd.php file: - """ - ] - * : A username. - * - * [--password=] - * : A password that should be masked. - * --- - * sensitive: true - * --- - * - * [--api-key=] - * : An API key that should be masked. - * --- - * sensitive: true - * --- - * - * @when before_wp_load - */ - WP_CLI::add_command( 'test-sensitive', function( $args, $assoc_args ) { - WP_CLI::line( 'username: ' . ( isset( $assoc_args['username'] ) ? $assoc_args['username'] : 'none' ) ); - WP_CLI::line( 'password: ' . ( isset( $assoc_args['password'] ) ? $assoc_args['password'] : 'none' ) ); - WP_CLI::line( 'api-key: ' . ( isset( $assoc_args['api-key'] ) ? $assoc_args['api-key'] : 'none' ) ); - } ); - """ - And a value-file file: - """ - admin - secretpassword123 - myapikey456 - """ - And a wp-cli.yml file: - """ - require: - - cmd.php - """ - - When I run `wp test-sensitive --prompt < value-file` - Then the return code should be 0 - And STDERR should be empty - And STDOUT should contain: - """ - username: admin - """ - And STDOUT should contain: - """ - password: secretpassword123 - """ - And STDOUT should contain: - """ - api-key: myapikey456 - """ - And STDOUT should contain: - """ - wp test-sensitive --username='admin' --password='[REDACTED]' --api-key='[REDACTED]' - """ - - Scenario: Flag prompt should accept Y for yes - Given an empty directory - And a cmd.php file: - """ - ] - * : Output format - * --- - * default: table - * options: - * - table - * - csv - * - json - * --- - * - * @when before_wp_load - */ - WP_CLI::add_command( 'test-assoc-default', function( $_, $assoc_args ){ - WP_CLI::line( 'format: ' . $assoc_args['format'] ); - }); - """ - And a empty-response file: - """ - - """ - And a wp-cli.yml file: - """ - require: - - cmd.php - """ - - When I run `wp test-assoc-default --prompt < empty-response` - Then STDOUT should match #wp test-assoc-default --format='table'|wp test-assoc-default --format="table"# - And STDOUT should contain: - """ - format: table - """ - - Scenario: Positional arg with default should apply default on empty input - Given an empty directory - And a cmd.php file: - """ - ] - * : The name - * --- - * default: World - * --- - * - * @when before_wp_load - */ - WP_CLI::add_command( 'test-positional-default', function( $args, $_ ){ - $name = isset( $args[0] ) ? $args[0] : 'Nobody'; - WP_CLI::line( 'Hello ' . $name ); - }); - """ - And a empty-response file: - """ - - """ - And a wp-cli.yml file: - """ - require: - - cmd.php - """ - - When I run `wp test-positional-default --prompt < empty-response` - Then STDOUT should match /wp test-positional-default ["\']World["\']/ - And STDOUT should contain: - """ - Hello World - """ - - Scenario: Prompt should display argument descriptions - Given an empty directory - And a cmd.php file: - """ - - * : The name of the item. - * - * [--type=] - * : The type of the item. - * - * [--enabled] - * : Whether the item is enabled. - * - * @when before_wp_load - */ - WP_CLI::add_command( 'test-desc', function( $args, $assoc_args ) { - WP_CLI::line( 'name: ' . ( isset( $args[0] ) ? $args[0] : 'none' ) ); - WP_CLI::line( 'type: ' . ( isset( $assoc_args['type'] ) ? $assoc_args['type'] : 'none' ) ); - WP_CLI::line( 'enabled: ' . ( isset( $assoc_args['enabled'] ) ? 'yes' : 'no' ) ); - } ); - """ - And a value-file file: - """ - test-item - special - Y - """ - And a wp-cli.yml file: - """ - require: - - cmd.php - """ - - When I run `wp test-desc --prompt < value-file` - Then STDERR should be empty - And STDOUT should contain: - """ - name: test-item - """ - And STDOUT should contain: - """ - type: special - """ - And STDOUT should contain: - """ - enabled: yes - """ - diff --git a/features/requests.feature b/features/requests.feature deleted file mode 100644 index 30096aea2..000000000 --- a/features/requests.feature +++ /dev/null @@ -1,133 +0,0 @@ -Feature: Requests integration with both v1 and v2 - - # This test downgrades to WordPress 5.8, but the SQLite plugin requires 6.0+ - # WP-CLI 2.7 causes deprecation warnings on PHP 8.2 - @require-mysql @less-than-php-8.2 - Scenario: Composer stack with Requests v1 - Given an empty directory - And a composer.json file: - """ - { - "name": "wp-cli/composer-test", - "type": "project", - "require": { - "wp-cli/wp-cli": "2.7.0", - "wp-cli/core-command": "^2", - "wp-cli/eval-command": "^2" - } - } - """ - # Note: Composer outputs messages to stderr. - And I run `composer install --no-interaction 2>&1` - - When I run `vendor/bin/wp cli version` - Then STDOUT should contain: - """ - WP-CLI 2.7.0 - """ - - Given a WP installation - And I run `vendor/bin/wp core update --version=5.8 --force` - And I run `rm -r wp-content/themes/*` - - When I run `vendor/bin/wp core version` - Then STDOUT should contain: - """ - 5.8 - """ - - When I run `vendor/bin/wp eval "var_dump( \WP_CLI\Utils\http_request( 'GET', 'https://example.com/' ) );"` - Then STDOUT should contain: - """ - object(Requests_Response) - """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should be empty - - # This test downgrades to WordPress 5.8, but the SQLite plugin requires 6.0+ - @require-mysql - Scenario: Current version with WordPress-bundled Requests v1 - Given a WP installation - And I run `wp core update --version=5.8 --force` - And I run `rm -r wp-content/themes/*` - - When I run `wp core version` - Then STDOUT should contain: - """ - 5.8 - """ - - When I run `wp eval "var_dump( \WP_CLI\Utils\http_request( 'GET', 'https://example.com/' ) );"` - Then STDOUT should contain: - """ - object(Requests_Response) - """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should be empty - - When I run `wp plugin install debug-bar` - Then STDOUT should contain: - """ - Success: Installed 1 of 1 plugins. - """ - - # Skip on Windows due to cURL error 60: SSL certificate problem: unable to get local issuer certificate - # TODO: Investigate. - @skip-windows - Scenario: Current version with WordPress-bundled Requests v2 - Given a WP installation - # Switch themes because twentytwentyfive requires a version newer than 6.2 - # and it would otherwise cause a fatal error further down. - And I try `wp theme install twentyten` - And I try `wp theme activate twentyten` - And I run `wp core update --version=6.2 --force` - - When I run `wp core version` - Then STDOUT should contain: - """ - 6.2 - """ - - When I run `wp eval "var_dump( \WP_CLI\Utils\http_request( 'GET', 'https://example.com/' ) );"` - Then STDOUT should contain: - """ - object(WpOrg\Requests\Response) - """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should be empty - - When I run `wp plugin install debug-bar` - Then STDOUT should contain: - """ - Success: Installed 1 of 1 plugins. - """ - - # This test downgrades to WordPress 5.8, but the SQLite plugin requires 6.0+ - @require-mysql - Scenario: Current version with WordPress-bundled Request v1 and an alias - Given a WP installation in 'foo' - And I run `wp --path=foo core download --version=5.8 --force` - And a wp-cli.yml file: - """ - @foo: - path: foo - """ - - When I try `WP_CLI_RUNTIME_ALIAS='{"@foo":{"path":"foo"}}' wp @foo option get home --debug` - Then STDERR should contain: - """ - Setting RequestsLibrary::$version to v1 - """ - And STDERR should contain: - """ - Setting RequestsLibrary::$source to wp-core - """ diff --git a/features/runcommand.feature b/features/runcommand.feature index 3000ac265..20e88c990 100644 --- a/features/runcommand.feature +++ b/features/runcommand.feature @@ -46,22 +46,17 @@ Feature: Run a WP-CLI command Scenario Outline: Run a WP-CLI command and render output Given a WP installation - And a test.php file: - """ - user_login ); - """ - When I run `wp run "option get home"` + When I run `wp run 'option get home'` Then STDOUT should be: """ - https://example.com + http://example.com returned: NULL """ And STDERR should be empty And the return code should be 0 - When I run `wp run "eval-file test.php"` + When I run `wp run 'eval "echo wp_get_current_user()->user_login . PHP_EOL;"'` Then STDOUT should be: """ admin @@ -70,7 +65,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `WP_CLI_CONFIG_PATH=config.yml wp run "user get"` + When I run `WP_CLI_CONFIG_PATH=config.yml wp run 'user get'` Then STDOUT should be: """ admin@example.com @@ -86,21 +81,16 @@ Feature: Run a WP-CLI command Scenario Outline: Run a WP-CLI command and capture output Given a WP installation - And a user-login.php file: - """ - user_login . PHP_EOL; - """ - When I run `wp run --return "option get home"` + When I run `wp run --return 'option get home'` Then STDOUT should be: """ - returned: 'https://example.com' + returned: 'http://example.com' """ And STDERR should be empty And the return code should be 0 - When I run `wp --return run "eval-file user-login.php"` + When I run `wp --return run 'eval "echo wp_get_current_user()->user_login . PHP_EOL;"'` Then STDOUT should be: """ returned: 'admin' @@ -108,7 +98,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `wp --return=stderr run "eval-file user-login.php"` + When I run `wp --return=stderr run 'eval "echo wp_get_current_user()->user_login . PHP_EOL;"'` Then STDOUT should be: """ returned: '' @@ -116,7 +106,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `wp --return=return_code run "eval-file user-login.php"` + When I run `wp --return=return_code run 'eval "echo wp_get_current_user()->user_login . PHP_EOL;"'` Then STDOUT should be: """ returned: 0 @@ -124,7 +114,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `wp --return=all run "eval-file user-login.php"` + When I run `wp --return=all run 'eval "echo wp_get_current_user()->user_login . PHP_EOL;"'` Then STDOUT should be: """ returned: array ( @@ -136,7 +126,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `WP_CLI_CONFIG_PATH=config.yml wp --return run "user get"` + When I run `WP_CLI_CONFIG_PATH=config.yml wp --return run 'user get'` Then STDOUT should be: """ returned: 'admin@example.com' @@ -152,7 +142,7 @@ Feature: Run a WP-CLI command Scenario Outline: Use 'parse=json' to parse JSON output Given a WP installation - When I run `wp run --return --parse=json "user get admin --fields=user_login,user_email --format=json"` + When I run `wp run --return --parse=json 'user get admin --fields=user_login,user_email --format=json'` Then STDOUT should be: """ returned: array ( @@ -168,13 +158,8 @@ Feature: Run a WP-CLI command Scenario Outline: Exit on error by default Given a WP installation - And a test-error.php file: - """ - "eval-file test-error.php"` + When I try `wp run 'eval "WP_CLI::error( var_export( get_current_user_id(), true ) );"'` Then STDOUT should be empty And STDERR should be: """ @@ -189,13 +174,8 @@ Feature: Run a WP-CLI command Scenario Outline: Override erroring on exit Given a WP installation - And a test-error.php file: - """ - --no-exit_error --return=all "eval-file test-error.php"` + When I try `wp run --no-exit_error --return=all 'eval "WP_CLI::error( var_export( get_current_user_id(), true ) );"'` Then STDOUT should be: """ returned: array ( @@ -207,7 +187,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `wp --no-exit_error run "option pluck foo$bar barfoo"` + When I run `wp --no-exit_error run 'option pluck foo$bar barfoo'` Then STDOUT should be: """ returned: NULL @@ -222,25 +202,9 @@ Feature: Run a WP-CLI command Scenario Outline: Output using echo and log, success, warning and error Given a WP installation - And a test-output-error.php file: - """ - --no-exit_error --return=all "eval-file test-output-error.php"` + When I run `wp run --no-exit_error --return=all 'eval "WP_CLI::log( '\'log\'' ); echo '\'echo\''; WP_CLI::success( '\'success\'' ); WP_CLI::error( '\'error\'' );"'` Then STDOUT should be: """ returned: array ( @@ -253,7 +217,7 @@ Feature: Run a WP-CLI command And STDERR should be empty And the return code should be 0 - When I run `wp run --no-exit_error --return=all "eval-file test-output-success.php"` + When I run `wp run --no-exit_error --return=all 'eval "echo '\'echo\''; WP_CLI::log( '\'log\'' ); WP_CLI::warning( '\'warning\''); WP_CLI::success( '\'success\'' );"'` Then STDOUT should be: """ returned: array ( @@ -271,13 +235,13 @@ Feature: Run a WP-CLI command | --no-launch | | --launch | - @less-than-php-8 Scenario Outline: Installed packages work as expected Given a WP installation - # Allow for composer/ca-bundle using `openssl_x509_parse()` which throws PHP warnings on old versions of PHP. - When I try `wp package install wp-cli/scaffold-package-command` - And I run `wp run "help scaffold package"` + When I run `wp package install wp-cli/scaffold-package-command` + Then STDERR should be empty + + When I run `wp run 'help scaffold package'` Then STDOUT should contain: """ wp scaffold package @@ -285,68 +249,49 @@ Feature: Run a WP-CLI command And STDERR should be empty Examples: - | flag | - | --no-launch | - | --launch | + | flag | + | --no-launch | + | --launch | Scenario Outline: Persists global parameters when supplied interactively Given a WP installation in 'foo' - When I run `wp --path=foo run "config set test 42 --type=constant"` + When I run `wp --path=foo run 'rewrite structure "archives/%post_id%/" --path=foo'` Then STDOUT should be: """ - Success: Added the constant 'test' to the 'wp-config.php' file with the value '42'. + Success: Rewrite rules flushed. + Success: Rewrite structure set. returned: NULL """ And STDERR should be empty And the return code should be 0 Examples: - | flag | - | --no-launch | - | --launch | - - Scenario: Persists alias when launching a new process via runcommand - Given a WP installation in 'foo' - And a wp-cli.yml file: - """ - @foo: - path: foo - user: admin - require: - - command.php - """ - - When I run `wp @foo --launch --return run "option get home"` - Then STDOUT should be: - """ - returned: 'https://example.com' - """ - And STDERR should be empty - And the return code should be 0 + | flag | + | --no-launch | + | --launch | Scenario Outline: Apply backwards compat conversions Given a WP installation - When I run `wp run "term url category 1"` + When I run `wp run 'term url category 1'` Then STDOUT should be: """ - https://example.com/?cat=1 + http://example.com/?cat=1 returned: NULL """ And STDERR should be empty And the return code should be 0 Examples: - | flag | - | --no-launch | - | --launch | + | flag | + | --no-launch | + | --launch | - @skip-windows Scenario Outline: Check that proc_open() and proc_close() aren't disabled for launch - Given a WP installation + Given a WP install - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=} --launch run "option get home"` + When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=} --launch run 'option get home'` Then STDERR should contain: """ Error: Cannot do 'launch option': The PHP functions `proc_open()` and/or `proc_close()` are disabled @@ -357,140 +302,3 @@ Feature: Run a WP-CLI command | func | | proc_open | | proc_close | - - Scenario: Check that command_args provided to runcommand are used in command - Given a WP installation - And a custom-cmd.php file: - """ - array( '--exec="echo \'test\' . PHP_EOL;"' ) ); - WP_CLI::runcommand( 'option get home', $cli_opts); - } - public function bad_path( $args ) { - $cli_opts = array( 'command_args' => array('--path=/bad/path' ) ); - WP_CLI::runcommand( 'option get home', $cli_opts); - } - } - WP_CLI::add_command( 'custom-command', 'Custom_Command' ); - """ - - When I run `wp --require=custom-cmd.php custom-command echo_test` - Then STDOUT should be: - """ - test - https://example.com - """ - - When I try `wp --require=custom-cmd.php custom-command bad_path` - Then STDERR should contain: - """ - The used path is: /bad/path/ - """ - - @skip-windows - Scenario: Check that required files are used from command arguments and ENV VAR (Unix) - Given a WP installation - And a custom-cmd.php file: - """ - ' for more information - """ - And the return code should be 0 - - Scenario: Suggest 'wp term ' when an invalid taxonomy command is run - Given a WP install - - When I try `wp category list` - Then STDERR should contain: - """ - Did you mean 'wp term '? - """ - And the return code should be 1 - - Scenario: Suggest 'wp post ' when an invalid post type command is run - Given a WP install - - When I try `wp page create` - Then STDERR should contain: - """ - Did you mean 'wp post --post_type=page '? - """ - And the return code should be 1 - - Scenario: Uncaught exceptions should be transformed to WP-CLI errors - Given an empty directory - And a test-exception.php file: - """ - 'before_wp_load' ) ); - - WP_CLI::add_command( 'test-runtime-exception', function() { - throw new \RuntimeException( 'Test runtime exception message' ); - }, array( 'when' => 'before_wp_load' ) ); - """ - - When I try `wp --require=test-exception.php test-exception` - Then STDERR should contain: - """ - Error: Exception: Test exception message - """ - And the return code should be 1 - - When I try `wp --require=test-exception.php test-runtime-exception` - Then STDERR should contain: - """ - Error: RuntimeException: Test runtime exception message - """ - And the return code should be 1 - - Scenario: Path argument with single-dot segment should produce canonical ABSPATH - When I try `wp no-such-command --path=/foo/./bar --debug` - Then STDERR should contain: - """ - ABSPATH defined: /foo/bar/ - """ - - When I try `wp no-such-command --path=/foo/./bar/ --debug` - Then STDERR should contain: - """ - ABSPATH defined: /foo/bar/ - """ - - Given an empty directory - And a wp-cli.yml file: - """ - path: ./public/wp - """ - - When I try `wp no-such-command --debug` - Then STDERR should not match /ABSPATH defined: .*\/\.\// diff --git a/features/shutdown-handler.feature b/features/shutdown-handler.feature deleted file mode 100644 index 20f4473f8..000000000 --- a/features/shutdown-handler.feature +++ /dev/null @@ -1,299 +0,0 @@ -@require-wp-5.2 -Feature: Shutdown handler suggests workarounds for plugin/theme errors - - Background: - Given a WP installation - And a session_no file: - """ - n - """ - And a session_yes file: - """ - y - """ - - Scenario: Fatal error in plugin triggers shutdown handler with suggestion - Given a wp-content/plugins/error-plugin/error-plugin.php file: - """ - Given( '/^an empty directory$/', + function ( $world ) { + $world->create_run_dir(); + } +); + +$steps->Given( '/^an? (empty|non-existent) ([^\s]+) directory$/', + function ( $world, $empty_or_nonexistent, $dir ) { + $dir = $world->replace_variables( $dir ); + if ( ! WP_CLI\Utils\is_path_absolute( $dir ) ) { + $dir = $world->variables['RUN_DIR'] . "/$dir"; + } + if ( 0 !== strpos( $dir, sys_get_temp_dir() ) ) { + throw new RuntimeException( sprintf( "Attempted to delete directory '%s' that is not in the temp directory '%s'. " . __FILE__ . ':' . __LINE__, $dir, sys_get_temp_dir() ) ); + } + $world->remove_dir( $dir ); + if ( 'empty' === $empty_or_nonexistent ) { + mkdir( $dir, 0777, true /*recursive*/ ); + } + } +); + +$steps->Given( '/^an empty cache/', + function ( $world ) { + $world->variables['SUITE_CACHE_DIR'] = FeatureContext::create_cache_dir(); + } +); + +$steps->Given( '/^an? ([^\s]+) file:$/', + function ( $world, $path, PyStringNode $content ) { + $content = (string) $content . "\n"; + $full_path = $world->variables['RUN_DIR'] . "/$path"; + $dir = dirname( $full_path ); + if ( ! file_exists( $dir ) ) { + mkdir( $dir, 0777, true /*recursive*/ ); + } + file_put_contents( $full_path, $content ); + } +); + +$steps->Given( '/^"([^"]+)" replaced with "([^"]+)" in the ([^\s]+) file$/', function( $world, $search, $replace, $path ) { + $full_path = $world->variables['RUN_DIR'] . "/$path"; + $contents = file_get_contents( $full_path ); + $contents = str_replace( $search, $replace, $contents ); + file_put_contents( $full_path, $contents ); +}); + +$steps->Given( '/^WP files$/', + function ( $world ) { + $world->download_wp(); + } +); + +$steps->Given( '/^wp-config\.php$/', + function ( $world ) { + $world->create_config(); + } +); + +$steps->Given( '/^a database$/', + function ( $world ) { + $world->create_db(); + } +); + +$steps->Given( '/^a WP (install|installation)$/', + function ( $world ) { + $world->install_wp(); + } +); + +$steps->Given( "/^a WP (install|installation) in '([^\s]+)'$/", + function ( $world, $_, $subdir ) { + $world->install_wp( $subdir ); + } +); + +$steps->Given( '/^a WP (install|installation) with Composer$/', + function ( $world ) { + $world->install_wp_with_composer(); + } +); + +$steps->Given( "/^a WP (install|installation) with Composer and a custom vendor directory '([^\s]+)'$/", + function ( $world, $_, $vendor_directory ) { + $world->install_wp_with_composer( $vendor_directory ); + } +); + +$steps->Given( '/^a WP multisite (subdirectory|subdomain)?\s?(install|installation)$/', + function ( $world, $type = 'subdirectory' ) { + $world->install_wp(); + $subdomains = ! empty( $type ) && 'subdomain' === $type ? 1 : 0; + $world->proc( 'wp core install-network', array( 'title' => 'WP CLI Network', 'subdomains' => $subdomains ) )->run_check(); + } +); + +$steps->Given( '/^these installed and active plugins:$/', + function( $world, $stream ) { + $plugins = implode( ' ', array_map( 'trim', explode( PHP_EOL, (string)$stream ) ) ); + $world->proc( "wp plugin install $plugins --activate" )->run_check(); + } +); + +$steps->Given( '/^a custom wp-content directory$/', + function ( $world ) { + $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; + + $wp_config_code = file_get_contents( $wp_config_path ); + + $world->move_files( 'wp-content', 'my-content' ); + $world->add_line_to_wp_config( $wp_config_code, + "define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/my-content' );" ); + + $world->move_files( 'my-content/plugins', 'my-plugins' ); + $world->add_line_to_wp_config( $wp_config_code, + "define( 'WP_PLUGIN_DIR', __DIR__ . '/my-plugins' );" ); + + file_put_contents( $wp_config_path, $wp_config_code ); + } +); + +$steps->Given( '/^download:$/', + function ( $world, TableNode $table ) { + foreach ( $table->getHash() as $row ) { + $path = $world->replace_variables( $row['path'] ); + if ( file_exists( $path ) ) { + // assume it's the same file and skip re-download + continue; + } + + Process::create( \WP_CLI\Utils\esc_cmd( 'curl -sSL %s > %s', $row['url'], $path ) )->run_check(); + } + } +); + +$steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/', + function ( $world, $stream, $output_filter, $key ) { + + $stream = strtolower( $stream ); + + if ( $output_filter ) { + $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; + if ( false !== preg_match( $output_filter, $world->result->$stream, $matches ) ) + $output = array_pop( $matches ); + else + $output = ''; + } else { + $output = $world->result->$stream; + } + $world->variables[ $key ] = trim( $output, "\n" ); + } +); + +$steps->Given( '/^a new Phar with (?:the same version|version "([^"]+)")$/', + function ( $world, $version = 'same' ) { + $world->build_phar( $version ); + } +); + +$steps->Given( '/^a downloaded Phar with (?:the same version|version "([^"]+)")$/', + function ( $world, $version = 'same' ) { + $world->download_phar( $version ); + } +); + +$steps->Given( '/^save the (.+) file ([\'].+[^\'])?as \{(\w+)\}$/', + function ( $world, $filepath, $output_filter, $key ) { + $full_file = file_get_contents( $world->replace_variables( $filepath ) ); + + if ( $output_filter ) { + $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; + if ( false !== preg_match( $output_filter, $full_file, $matches ) ) + $output = array_pop( $matches ); + else + $output = ''; + } else { + $output = $full_file; + } + $world->variables[ $key ] = trim( $output, "\n" ); + } +); + +$steps->Given('/^a misconfigured WP_CONTENT_DIR constant directory$/', + function($world) { + $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; + + $wp_config_code = file_get_contents( $wp_config_path ); + + $world->add_line_to_wp_config( $wp_config_code, + "define( 'WP_CONTENT_DIR', '' );" ); + + file_put_contents( $wp_config_path, $wp_config_code ); + } +); + +$steps->Given( '/^a dependency on current wp-cli$/', + function ( $world ) { + $world->composer_require_current_wp_cli(); + } +); + +$steps->Given( '/^a PHP built-in web server$/', + function ( $world ) { + $world->start_php_server(); + } +); + +$steps->Given( "/^a PHP built-in web server to serve '([^\s]+)'$/", + function ( $world, $subdir ) { + $world->start_php_server( $subdir ); + } +); diff --git a/features/steps/then.php b/features/steps/then.php new file mode 100644 index 000000000..21589e737 --- /dev/null +++ b/features/steps/then.php @@ -0,0 +1,237 @@ +Then( '/^the return code should( not)? be (\d+)$/', + function ( $world, $not, $return_code ) { + if ( ( ! $not && $return_code != $world->result->return_code ) || ( $not && $return_code == $world->result->return_code ) ) { + throw new RuntimeException( $world->result ); + } + } +); + +$steps->Then( '/^(STDOUT|STDERR) should (be|contain|not contain):$/', + function ( $world, $stream, $action, PyStringNode $expected ) { + + $stream = strtolower( $stream ); + + $expected = $world->replace_variables( (string) $expected ); + + checkString( $world->result->$stream, $expected, $action, $world->result ); + } +); + +$steps->Then( '/^(STDOUT|STDERR) should be a number$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + assertNumeric( trim( $world->result->$stream, "\n" ) ); + } +); + +$steps->Then( '/^(STDOUT|STDERR) should not be a number$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + assertNotNumeric( trim( $world->result->$stream, "\n" ) ); + } +); + +$steps->Then( '/^STDOUT should be a table containing rows:$/', + function ( $world, TableNode $expected ) { + $output = $world->result->stdout; + $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); + + $expected_rows = array(); + foreach ( $expected->getRows() as $row ) { + $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); + } + + compareTables( $expected_rows, $actual_rows, $output ); + } +); + +$steps->Then( '/^STDOUT should end with a table containing rows:$/', + function ( $world, TableNode $expected ) { + $output = $world->result->stdout; + $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); + + $expected_rows = array(); + foreach ( $expected->getRows() as $row ) { + $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); + } + + $start = array_search( $expected_rows[0], $actual_rows ); + + if ( false === $start ) + throw new \Exception( $world->result ); + + compareTables( $expected_rows, array_slice( $actual_rows, $start ), $output ); + } +); + +$steps->Then( '/^STDOUT should be JSON containing:$/', + function ( $world, PyStringNode $expected ) { + $output = $world->result->stdout; + $expected = $world->replace_variables( (string) $expected ); + + if ( !checkThatJsonStringContainsJsonString( $output, $expected ) ) { + throw new \Exception( $world->result ); + } +}); + +$steps->Then( '/^STDOUT should be a JSON array containing:$/', + function ( $world, PyStringNode $expected ) { + $output = $world->result->stdout; + $expected = $world->replace_variables( (string) $expected ); + + $actualValues = json_decode( $output ); + $expectedValues = json_decode( $expected ); + + $missing = array_diff( $expectedValues, $actualValues ); + if ( !empty( $missing ) ) { + throw new \Exception( $world->result ); + } +}); + +$steps->Then( '/^STDOUT should be CSV containing:$/', + function ( $world, TableNode $expected ) { + $output = $world->result->stdout; + + $expected_rows = $expected->getRows(); + foreach ( $expected as &$row ) { + foreach ( $row as &$value ) { + $value = $world->replace_variables( $value ); + } + } + + if ( ! checkThatCsvStringContainsValues( $output, $expected_rows ) ) + throw new \Exception( $world->result ); + } +); + +$steps->Then( '/^STDOUT should be YAML containing:$/', + function ( $world, PyStringNode $expected ) { + $output = $world->result->stdout; + $expected = $world->replace_variables( (string) $expected ); + + if ( !checkThatYamlStringContainsYamlString( $output, $expected ) ) { + throw new \Exception( $world->result ); + } +}); + +$steps->Then( '/^(STDOUT|STDERR) should be empty$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + if ( !empty( $world->result->$stream ) ) { + throw new \Exception( $world->result ); + } + } +); + +$steps->Then( '/^(STDOUT|STDERR) should not be empty$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + if ( '' === rtrim( $world->result->$stream, "\n" ) ) { + throw new Exception( $world->result ); + } + } +); + +$steps->Then( '/^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|!=|<>) ([+\w.{}-]+)$/', + function ( $world, $stream, $operator, $goal_ver ) { + $goal_ver = $world->replace_variables( $goal_ver ); + $stream = strtolower( $stream ); + if ( false === version_compare( trim( $world->result->$stream, "\n" ), $goal_ver, $operator ) ) { + throw new Exception( $world->result ); + } + } +); + +$steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:|not contain:)$/', + function ( $world, $path, $type, $action, $expected = null ) { + $path = $world->replace_variables( $path ); + + // If it's a relative path, make it relative to the current test dir + if ( '/' !== $path[0] ) + $path = $world->variables['RUN_DIR'] . "/$path"; + + if ( 'file' == $type ) { + $test = 'file_exists'; + } else if ( 'directory' == $type ) { + $test = 'is_dir'; + } + + switch ( $action ) { + case 'exist': + if ( ! $test( $path ) ) { + throw new Exception( "$path doesn't exist." ); + } + break; + case 'not exist': + if ( $test( $path ) ) { + throw new Exception( "$path exists." ); + } + break; + default: + if ( ! $test( $path ) ) { + throw new Exception( "$path doesn't exist." ); + } + $action = substr( $action, 0, -1 ); + $expected = $world->replace_variables( (string) $expected ); + if ( 'file' == $type ) { + $contents = file_get_contents( $path ); + } else if ( 'directory' == $type ) { + $files = glob( rtrim( $path, '/' ) . '/*' ); + foreach( $files as &$file ) { + $file = str_replace( $path . '/', '', $file ); + } + $contents = implode( PHP_EOL, $files ); + } + checkString( $contents, $expected, $action ); + } + } +); + +$steps->Then( '/^the contents of the (.+) file should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', + function ( $world, $path, $expected ) { + $path = $world->replace_variables( $path ); + // If it's a relative path, make it relative to the current test dir + if ( '/' !== $path[0] ) { + $path = $world->variables['RUN_DIR'] . "/$path"; + } + $contents = file_get_contents( $path ); + assertRegExp( $expected, $contents ); + } +); + +$steps->Then( '/^(STDOUT|STDERR) should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', + function ( $world, $stream, $expected ) { + $stream = strtolower( $stream ); + assertRegExp( $expected, $world->result->$stream ); + } +); + +$steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) { + if ( 'be sent' === $expected ) { + assertNotEquals( 0, $world->email_sends ); + } else if ( 'not be sent' === $expected ) { + assertEquals( 0, $world->email_sends ); + } else { + throw new Exception( 'Invalid expectation' ); + } +}); + +$steps->Then( '/^the HTTP status code should be (\d+)$/', + function ( $world, $return_code ) { + $response = \Requests::request( 'http://localhost:8080' ); + assertEquals( $return_code, $response->status_code ); + } +); diff --git a/features/steps/when.php b/features/steps/when.php new file mode 100644 index 000000000..d23aa0e66 --- /dev/null +++ b/features/steps/when.php @@ -0,0 +1,54 @@ + 'run_check_stderr', + 'try' => 'run' + ); + $method = $map[ $mode ]; + + return $proc->$method(); +} + +function capture_email_sends( $stdout ) { + $stdout = preg_replace( '#WP-CLI test suite: Sent email to.+\n?#', '', $stdout, -1, $email_sends ); + return array( $stdout, $email_sends ); +} + +$steps->When( '/^I launch in the background `([^`]+)`$/', + function ( $world, $cmd ) { + $world->background_proc( $cmd ); + } +); + +$steps->When( '/^I (run|try) `([^`]+)`$/', + function ( $world, $mode, $cmd ) { + $cmd = $world->replace_variables( $cmd ); + $world->result = invoke_proc( $world->proc( $cmd ), $mode ); + list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); + } +); + +$steps->When( "/^I (run|try) `([^`]+)` from '([^\s]+)'$/", + function ( $world, $mode, $cmd, $subdir ) { + $cmd = $world->replace_variables( $cmd ); + $world->result = invoke_proc( $world->proc( $cmd, array(), $subdir ), $mode ); + list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); + } +); + +$steps->When( '/^I (run|try) the previous command again$/', + function ( $world, $mode ) { + if ( !isset( $world->result ) ) + throw new \Exception( 'No previous command.' ); + + $proc = Process::create( $world->result->command, $world->result->cwd, $world->result->env ); + $world->result = invoke_proc( $proc, $mode ); + list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); + } +); + diff --git a/features/utils-wp.feature b/features/utils-wp.feature deleted file mode 100644 index 8c015fe60..000000000 --- a/features/utils-wp.feature +++ /dev/null @@ -1,1039 +0,0 @@ -Feature: Utilities that depend on WordPress code - - Scenario: Clear WP cache - Given a WP installation - And a test.php file: - """ - cache ) . ',' . isset( $wp_object_cache->group_ops ) . ',' . isset( $wp_object_cache->stats ) . ',' . isset( $wp_object_cache->memcache_debug ) . "\n"; - WP_CLI\Utils\wp_clear_object_cache(); - echo empty( $wp_object_cache->cache ) . ',' . isset( $wp_object_cache->group_ops ) . ',' . isset( $wp_object_cache->stats ) . ',' . isset( $wp_object_cache->memcache_debug ) . "\n"; - } ); - """ - - When I run `wp post create --post_title="Foo Bar" --porcelain` - And I run `wp --require=test.php eval ""` - Then STDOUT should be: - """ - ,,, - 1,,, - """ - - # `wp db query` does not yet work on SQLite, - # See https://github.com/wp-cli/db-command/issues/234 - @require-mysql - Scenario: Get WP table names for single site install - Given a WP installation - And I run `wp db query "CREATE TABLE xx_wp_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_xx_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_posts_xx ( id int );"` - And I run `wp db query "CREATE TABLE wp_categories ( id int );"` - And I run `wp db query "CREATE VIEW wp_posts_view AS ( SELECT ID from wp_posts );"` - And a table_names.php file: - """ - ...] - * : List tables based on wildcard search, e.g. 'wp_*_options' or 'wp_post?'. - * - * [--scope=] - * : Can be all, global, ms_global, blog, or old tables. Defaults to all. - * - * [--network] - * : List all the tables in a multisite installation. Overrides --scope=. - * - * [--all-tables-with-prefix] - * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network. - * - * [--all-tables] - * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix. - * - * [--base-tables-only] - * : Restrict returned tables to those that are not views. - * - * [--views-only] - * : Restrict returned tables to those that are views. - */ - function test_wp_get_table_names( $args, $assoc_args ) { - if ( $tables = WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ) ) { - echo implode( PHP_EOL, $tables ) . PHP_EOL; - } - } - WP_CLI::add_command( 'get_table_names', 'test_wp_get_table_names' ); - """ - - When I run `wp --require=table_names.php get_table_names` - Then STDOUT should contain: - """ - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - wp_usermeta - wp_users - """ - And save STDOUT as {DEFAULT_STDOUT} - - When I run `wp --require=table_names.php get_table_names --all-tables-with-prefix --views-only` - Then STDOUT should be: - """ - wp_posts_view - """ - - When I run `wp --require=table_names.php get_table_names --all-tables --base-tables-only` - Then STDOUT should not contain: - """ - wp_posts_view - """ - But STDOUT should contain: - """ - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_posts_xx - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - wp_usermeta - wp_users - """ - - When I run `wp --require=table_names.php get_table_names --scope=all` - Then STDOUT should be: - """ - {DEFAULT_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --scope=blog` - Then STDOUT should contain: - """ - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - """ - - When I run `wp --require=table_names.php get_table_names --scope=global` - Then STDOUT should be: - """ - wp_usermeta - wp_users - """ - - When I run `wp --require=table_names.php get_table_names --scope=ms_global` - Then STDOUT should be empty - - When I run `wp --require=table_names.php get_table_names --scope=old` - Then STDOUT should be: - """ - wp_categories - """ - - When I run `wp --require=table_names.php get_table_names --network` - Then STDOUT should be: - """ - {DEFAULT_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --all-tables-with-prefix` - Then STDOUT should contain: - """ - wp_categories - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_posts_view - wp_posts_xx - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - wp_usermeta - wp_users - wp_xx_posts - """ - - When I run `wp --require=table_names.php get_table_names --all-tables` - Then STDOUT should contain: - """ - wp_categories - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_posts_view - wp_posts_xx - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - wp_usermeta - wp_users - wp_xx_posts - xx_wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts'` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*'` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts'` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --scope=blog` - Then STDOUT should be: - """ - wp_posts - """ - - When I try `wp --require=table_names.php get_table_names '*_posts' --scope=global` - Then STDERR should be: - """ - Error: Couldn't find any tables matching: *_posts - """ - And STDOUT should be empty - - When I run `wp --require=table_names.php get_table_names '*_posts' --network` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_posts - wp_xx_posts - """ - - When I run `wp --require=table_names.php get_table_names '*wp_posts' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - wp_posts_view - wp_posts_xx - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_posts - wp_xx_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --all-tables` - Then STDOUT should be: - """ - wp_posts - wp_xx_posts - xx_wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*wp_posts' --all-tables` - Then STDOUT should be: - """ - wp_posts - xx_wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*' --all-tables` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - wp_posts_view - wp_posts_xx - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts' --all-tables` - Then STDOUT should be: - """ - wp_posts - wp_xx_posts - """ - - When I try `wp --require=table_names.php get_table_names non_existent_table` - Then STDERR should be: - """ - Error: Couldn't find any tables matching: non_existent_table - """ - And STDOUT should be empty - - When I run `wp --require=table_names.php get_table_names wp_posts non_existent_table` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names wp_posts non_existent_table 'wp_?ption*'` - Then STDOUT should be: - """ - wp_options - wp_posts - """ - - # `wp db query` does not yet work on SQLite, - # See https://github.com/wp-cli/db-command/issues/234 - @require-mysql - Scenario: Get WP table names for multisite install - Given a WP multisite install - And I run `wp db query "CREATE TABLE xx_wp_posts ( id int );"` - And I run `wp db query "CREATE TABLE xx_wp_2_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_xx_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_2_xx_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_posts_xx ( id int );"` - And I run `wp db query "CREATE TABLE wp_2_posts_xx ( id int );"` - And I run `wp db query "CREATE TABLE wp_categories ( id int );"` - And I run `wp db query "CREATE TABLE wp_sitecategories ( id int );"` - And a table_names.php file: - """ - ...] - * : List tables based on wildcard search, e.g. 'wp_*_options' or 'wp_post?'. - * - * [--scope=] - * : Can be all, global, ms_global, blog, or old tables. Defaults to all. - * - * [--network] - * : List all the tables in a multisite installation. Overrides --scope=. - * - * [--all-tables-with-prefix] - * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network. - * - * [--all-tables] - * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix. - */ - function test_wp_get_table_names( $args, $assoc_args ) { - if ( $tables = WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ) ) { - echo implode( PHP_EOL, $tables ) . PHP_EOL; - } - } - WP_CLI::add_command( 'get_table_names', 'test_wp_get_table_names' ); - """ - - # With no subsite. - When I run `wp --require=table_names.php get_table_names` - # Leave out wp_blog_versions as it was never used and is removed with WP 5.3+. - # Leave out wp_blogmeta for old WP compat. - Then STDOUT should contain: - """ - wp_blogs - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_registration_log - wp_signups - wp_site - wp_sitemeta - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - wp_usermeta - wp_users - """ - And save STDOUT as {DEFAULT_STDOUT} - - When I run `wp --require=table_names.php get_table_names --scope=all` - Then STDOUT should be: - """ - {DEFAULT_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --scope=blog` - Then STDOUT should contain: - """ - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - """ - - When I run `wp --require=table_names.php get_table_names --scope=global` - # Leave out wp_blog_versions as it was never used and is removed with WP 5.3+. - # Leave out wp_blogmeta for old WP compat. - Then STDOUT should contain: - """ - wp_blogs - wp_registration_log - wp_signups - wp_site - wp_sitemeta - wp_usermeta - wp_users - """ - And save STDOUT as {GLOBAL_STDOUT} - - When I run `wp --require=table_names.php get_table_names --scope=ms_global` - # Leave out wp_blog_versions as it was never used and is removed with WP 5.3+. - # Leave out wp_blogmeta for old WP compat. - Then STDOUT should contain: - """ - wp_blogs - wp_registration_log - wp_signups - wp_site - wp_sitemeta - """ - - When I run `wp --require=table_names.php get_table_names --scope=old` - Then STDOUT should be: - """ - wp_categories - """ - - When I run `wp --require=table_names.php get_table_names --network` - Then STDOUT should be: - """ - {DEFAULT_STDOUT} - """ - - # With subsite. - Given I run `wp site create --slug=foo` - When I run `wp --require=table_names.php get_table_names` - Then STDOUT should be: - """ - {DEFAULT_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --url=example.com/foo --scope=blog` - Then STDOUT should contain: - """ - wp_2_commentmeta - wp_2_comments - wp_2_links - wp_2_options - wp_2_postmeta - wp_2_posts - wp_2_term_relationships - wp_2_term_taxonomy - """ - # Leave out wp_2_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_2_terms - """ - And save STDOUT as {SUBSITE_BLOG_STDOUT} - - When I run `wp --require=table_names.php get_table_names --url=example.com/foo` - Then STDOUT should be: - """ - {SUBSITE_BLOG_STDOUT} - {GLOBAL_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --network` - Then STDOUT should be: - """ - {SUBSITE_BLOG_STDOUT} - {DEFAULT_STDOUT} - """ - And save STDOUT as {NETWORK_STDOUT} - - When I run `wp --require=table_names.php get_table_names --network --url=example.com/foo` - Then STDOUT should be: - """ - {NETWORK_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --all-tables-with-prefix` - Then STDOUT should contain: - """ - wp_2_commentmeta - wp_2_comments - wp_2_links - wp_2_options - wp_2_postmeta - wp_2_posts - wp_2_posts_xx - wp_2_term_relationships - wp_2_term_taxonomy - """ - # Leave out wp_2_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_2_terms - wp_2_xx_posts - """ - # Leave out wp_blog_versions as it was never used and is removed with WP 5.3+. - # Leave out wp_blogmeta for old WP compat. - And STDOUT should contain: - """ - wp_blogs - wp_categories - wp_commentmeta - wp_comments - wp_links - wp_options - wp_postmeta - wp_posts - wp_posts_xx - wp_registration_log - wp_signups - wp_site - wp_sitecategories - wp_sitemeta - wp_term_relationships - wp_term_taxonomy - """ - # Leave out wp_termmeta for old WP compat. - And STDOUT should contain: - """ - wp_terms - wp_usermeta - wp_users - wp_xx_posts - """ - And save STDOUT as {ALL_TABLES_WITH_PREFIX_STDOUT} - - # Network overridden by all-tables-with-prefix. - When I run `wp --require=table_names.php get_table_names --all-tables-with-prefix --network` - Then STDOUT should contain: - """ - {ALL_TABLES_WITH_PREFIX_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names --all-tables` - Then STDOUT should be: - """ - {ALL_TABLES_WITH_PREFIX_STDOUT} - xx_wp_2_posts - xx_wp_posts - """ - And save STDOUT as {ALL_TABLES_STDOUT} - - # Network overridden by all-tables. - When I run `wp --require=table_names.php get_table_names --all-tables --network` - Then STDOUT should be: - """ - {ALL_TABLES_STDOUT} - """ - - When I run `wp --require=table_names.php get_table_names '*_posts'` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --network` - Then STDOUT should be: - """ - wp_2_posts - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*'` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*' --network` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts'` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts' --network` - Then STDOUT should be: - """ - wp_2_posts - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --scope=blog` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --scope=blog --network` - Then STDOUT should be: - """ - wp_2_posts - wp_posts - """ - - When I try `wp --require=table_names.php get_table_names '*_posts' --scope=global` - Then STDERR should be: - """ - Error: Couldn't find any tables matching: *_posts - """ - And STDOUT should be empty - - # Note: BC change 1.5.0, network does not override scope. - When I try `wp --require=table_names.php get_table_names '*_posts' --scope=global --network` - Then STDERR should be: - """ - Error: Couldn't find any tables matching: *_posts - """ - And STDOUT should be empty - - When I run `wp --require=table_names.php get_table_names '*_posts' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_2_posts - wp_2_xx_posts - wp_posts - wp_xx_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - wp_posts_xx - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts' --all-tables-with-prefix` - Then STDOUT should be: - """ - wp_2_posts - wp_2_xx_posts - wp_posts - wp_xx_posts - """ - - When I run `wp --require=table_names.php get_table_names '*_posts' --all-tables` - Then STDOUT should be: - """ - wp_2_posts - wp_2_xx_posts - wp_posts - wp_xx_posts - xx_wp_2_posts - xx_wp_posts - """ - - When I run `wp --require=table_names.php get_table_names '*wp_posts' --all-tables` - Then STDOUT should be: - """ - wp_posts - xx_wp_posts - """ - - When I run `wp --require=table_names.php get_table_names 'wp_post*' --all-tables` - Then STDOUT should be: - """ - wp_postmeta - wp_posts - wp_posts_xx - """ - - When I run `wp --require=table_names.php get_table_names 'wp*osts' --all-tables` - Then STDOUT should be: - """ - wp_2_posts - wp_2_xx_posts - wp_posts - wp_xx_posts - """ - - When I try `wp --require=table_names.php get_table_names non_existent_table` - Then STDERR should be: - """ - Error: Couldn't find any tables matching: non_existent_table - """ - And STDOUT should be empty - - When I run `wp --require=table_names.php get_table_names wp_posts non_existent_table` - Then STDOUT should be: - """ - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names wp_posts non_existent_table 'wp_?ption*'` - Then STDOUT should be: - """ - wp_options - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names wp_posts non_existent_table 'wp_*ption?'` - Then STDOUT should be: - """ - wp_options - wp_posts - """ - - When I run `wp --require=table_names.php get_table_names wp_posts non_existent_table 'wp_*ption?' --network` - Then STDOUT should be: - """ - wp_2_options - wp_options - wp_posts - """ - - @less-than-wp-6.1 - Scenario: Get WP table names for multisite install (site_categories only) - Given a WP multisite install - And I run `wp db query "CREATE TABLE xx_wp_posts ( id int );"` - And I run `wp db query "CREATE TABLE xx_wp_2_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_xx_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_2_xx_posts ( id int );"` - And I run `wp db query "CREATE TABLE wp_posts_xx ( id int );"` - And I run `wp db query "CREATE TABLE wp_2_posts_xx ( id int );"` - And I run `wp db query "CREATE TABLE wp_categories ( id int );"` - And I run `wp db query "CREATE TABLE wp_sitecategories ( id int );"` - And a table_names.php file: - """ - ...] - * : List tables based on wildcard search, e.g. 'wp_*_options' or 'wp_post?'. - * - * [--scope=] - * : Can be all, global, ms_global, blog, or old tables. Defaults to all. - * - * [--network] - * : List all the tables in a multisite installation. Overrides --scope=. - * - * [--all-tables-with-prefix] - * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network. - * - * [--all-tables] - * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix. - */ - function test_wp_get_table_names( $args, $assoc_args ) { - if ( $tables = WP_CLI\Utils\wp_get_table_names( $args, $assoc_args ) ) { - echo implode( PHP_EOL, $tables ) . PHP_EOL; - } - } - WP_CLI::add_command( 'get_table_names', 'test_wp_get_table_names' ); - """ - And an enable_sitecategories.php file: - """ - driver ) { - $this->driver = new \Stash\Driver\FileSystem(); - } - return $this->driver; - } - } - } - - namespace Stash\Driver { - class FileSystem { - public function get() {} - public function set() {} - } - } - - namespace { - global $_wp_using_ext_object_cache; - $_wp_using_ext_object_cache = true; - - // Initialize WP-Stash - \Inpsyde\WpStash\WpStash::instance(); - - // WordPress object cache implementation - class WP_Object_Cache { - public function get( $key, $group = 'default', $force = false, &$found = null ) { - return false; - } - public function set( $key, $data, $group = 'default', $expire = 0 ) { - return true; - } - public function delete( $key, $group = 'default' ) { - return true; - } - public function flush() { - return true; - } - } - - function wp_cache_init() { - global $wp_object_cache; - $wp_object_cache = new WP_Object_Cache(); - } - - function wp_cache_get() { return false; } - function wp_cache_add() { return false; } - function wp_cache_set() { return false; } - function wp_cache_delete() { return false; } - function wp_cache_add_non_persistent_groups() { return false; } - function wp_cache_close() { return true; } - } - """ - And a cache_type_test.php file: - """ - } --skip-wordpress eval "WP_CLI\Utils\run_mysql_command( null, array() );"` - Then STDERR should contain: - """ - Error: Cannot do 'run_mysql_command': The PHP functions `proc_open()` and/or `proc_close()` are disabled - """ - And STDOUT should be empty - And the return code should be 1 - - Examples: - | func | - | proc_open | - | proc_close | - - @skip-windows - Scenario Outline: Check that `proc_open()` and `proc_close()` aren't disabled for `Utils\launch_editor_for_input()` - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--ddisable_functions=} --skip-wordpress eval "WP_CLI\Utils\launch_editor_for_input( null, null );"` - Then STDERR should contain: - """ - Error: Cannot do 'launch_editor_for_input': The PHP functions `proc_open()` and/or `proc_close()` are disabled - """ - And STDOUT should be empty - And the return code should be 1 - - Examples: - | func | - | proc_open | - | proc_close | - - @require-mysql - Scenario: Check that `Utils\run_mysql_command()` uses STDOUT and STDERR by default - When I run `wp --skip-wordpress eval "WP_CLI\Utils\run_mysql_command( '{MYSQL_BINARY} --no-defaults', [ 'user' => '{DB_USER}', 'pass' => '{DB_PASSWORD}', 'host' => '{DB_HOST}', 'execute' => 'SHOW DATABASES;' ] );"` - Then STDOUT should contain: - """ - Database - """ - And STDOUT should contain: - """ - information_schema - """ - And STDERR should be empty - - When I try `wp --skip-wordpress eval "WP_CLI\Utils\run_mysql_command( '{MYSQL_BINARY} --no-defaults', [ 'user' => '{DB_USER}', 'pass' => '{DB_PASSWORD}', 'host' => '{DB_HOST}', 'execute' => 'broken query' ]);"` - Then STDOUT should be empty - And STDERR should contain: - """ - You have an error in your SQL syntax - """ - - @require-mysql - Scenario: Check that `Utils\run_mysql_command()` can return data and errors if requested - Given an empty directory - When I run `wp --skip-wordpress eval "list( \$stdout, \$stderr, \$exit_code ) = WP_CLI\\Utils\\run_mysql_command( \"{MYSQL_BINARY} --no-defaults\", [ \"user\" => \"{DB_USER}\", \"pass\" => \"{DB_PASSWORD}\", \"host\" => \"{DB_HOST}\", \"execute\" => \"SHOW DATABASES;\" ], null, false ); fwrite( STDOUT, strtoupper( \$stdout ) ); fwrite( STDERR, strtoupper( \$stderr ) );"` - Then STDOUT should not contain: - """ - Database - """ - And STDOUT should contain: - """ - DATABASE - """ - And STDOUT should not contain: - """ - information_schema - """ - And STDOUT should contain: - """ - INFORMATION_SCHEMA - """ - And STDERR should be empty - - When I try `wp --skip-wordpress eval "list( \$stdout, \$stderr, \$exit_code ) = WP_CLI\\Utils\\run_mysql_command( \"{MYSQL_BINARY} --no-defaults\", [ \"user\" => \"{DB_USER}\", \"pass\" => \"{DB_PASSWORD}\", \"host\" => \"{DB_HOST}\", \"execute\" => \"broken query\" ], null, false ); fwrite( STDOUT, strtoupper( \$stdout ) ); fwrite( STDERR, strtoupper( \$stderr ) );"` - Then STDOUT should be empty - And STDERR should not contain: - """ - You have an error in your SQL syntax - """ - And STDERR should contain: - """ - YOU HAVE AN ERROR IN YOUR SQL SYNTAX - """ - - Scenario: Check `Utils\get_temp_dir()` when `sys_temp_dir` directive set - # `sys_temp_dir` set to unwritable. - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--dsys_temp_dir=\\tmp\\} --skip-wordpress eval "echo WP_CLI\Utils\get_temp_dir();"` - Then STDERR should contain: - """ - Warning: Temp directory isn't writable - """ - And STDERR should contain: - """ - \tmp/ - """ - And STDOUT should be: - """ - \tmp/ - """ - And the return code should be 0 - - # `sys_temp_dir` unset. - When I run `{INVOKE_WP_CLI_WITH_PHP_ARGS--dsys_temp_dir=} --skip-wordpress eval "echo WP_CLI\Utils\get_temp_dir();"` - Then STDOUT should match /\/$/ - - @require-mysql - Scenario: Ensure that Utils\run_mysql_command() passes through without reading full DB into memory - Given a WP install - - And I run `printf '%*s' 1048576 | tr ' ' "."` - And STDOUT should not be empty - And save STDOUT as {ONE_MB_OF_DATA} - And a create_sql_file.sh file: - """ - #!/bin/bash - echo "CREATE TABLE \`custom_table\` (\`key\` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT, \`text\` LONGTEXT, PRIMARY KEY (\`key\`) );" > test_db.sql - echo "INSERT INTO \`custom_table\` (\`text\`) VALUES" >> test_db.sql - index=1 - while [[ $index -le 60 ]]; - do - echo "('{ONE_MB_OF_DATA}')," >> test_db.sql - index=`expr $index + 1` - done - echo "('{ONE_MB_OF_DATA}');" >> test_db.sql - """ - And I run `bash create_sql_file.sh` - And I run `test $(wc -c < test_db.sql) -gt 52428800` - And a calculate_host_string.sh file: - """ - #!/bin/bash - FULL_HOST="{DB_HOST}" - PORT="" - HOST_STRING="" - case ${FULL_HOST##*[]]} in - (*:*) HOST=${FULL_HOST%:*} PORT=${FULL_HOST##*:};; - (*) HOST=$FULL_HOST;; - esac - HOST_STRING="--host=$HOST" - if [ -n "$PORT" ]; then - HOST_STRING="$HOST_STRING --port=$PORT --protocol=tcp" - fi - echo "$HOST_STRING" - """ - And I run `bash calculate_host_string.sh` - And STDOUT should contain: - """ - --host - """ - And save STDOUT as {DB_HOST_STRING} - - When I try `{MYSQL_BINARY} --database={DB_NAME} --user={DB_ROOT_USER} --password={DB_ROOT_PASSWORD} {DB_HOST_STRING} -e "SET GLOBAL max_allowed_packet=64*1024*1024;"` - Then the return code should be 0 - - # This throws a warning because of the password. - When I try `{MYSQL_BINARY} --database={DB_NAME} --user={DB_USER} --password={DB_PASSWORD} {DB_HOST_STRING} < test_db.sql` - Then the return code should be 0 - - # The --skip-column-statistics flag is not always present. - When I try `{SQL_DUMP_COMMAND} --help | grep -q 'column-statistics' && echo '--skip-column-statistics'` - Then save STDOUT as {SKIP_COLUMN_STATISTICS_FLAG} - - # This throws a warning because of the password. - When I try `{INVOKE_WP_CLI_WITH_PHP_ARGS--dmemory_limit=256M -ddisable_functions=ini_set} eval "\WP_CLI\Utils\run_mysql_command('/usr/bin/env {SQL_DUMP_COMMAND} {SKIP_COLUMN_STATISTICS_FLAG} --no-tablespaces {DB_NAME}', [ 'user' => '{DB_USER}', 'pass' => '{DB_PASSWORD}', 'host' => '{DB_HOST}' ], null, true);"` - Then the return code should be 0 - And STDOUT should not be empty - And STDOUT should contain: - """ - CREATE TABLE - """ - And STDOUT should contain: - """ - {ONE_MB_OF_DATA} - """ diff --git a/features/validation.feature b/features/validation.feature index 69f3c0ac4..15e9c040d 100644 --- a/features/validation.feature +++ b/features/validation.feature @@ -8,7 +8,7 @@ Feature: Argument validation When I try `wp plugin install` Then the return code should be 1 - And STDOUT should contain: + Then STDOUT should contain: """ usage: wp plugin install """ @@ -17,11 +17,15 @@ Feature: Argument validation Given an empty directory And WP files - When I try `wp core config --dbprefix=invalid- --dbname=foo --dbpass=bar --dbuser=baz --skip-check` + When I try `wp core config` Then the return code should be 1 And STDERR should contain: """ - Error: --dbprefix can only contain numbers, letters, and underscores. + Parameter errors: + """ + And STDERR should contain: + """ + missing --dbname parameter """ When I try `wp core config --invalid --other-invalid` diff --git a/features/wp-config.feature b/features/wp-config.feature deleted file mode 100644 index d2951f795..000000000 --- a/features/wp-config.feature +++ /dev/null @@ -1,35 +0,0 @@ -Feature: wp-config - - Scenario: Default WordPress install with WP_CONFIG_PATH specified in environment variable - Given a WP installation - And a wp-config-override.php file: - """ - ' . TEST_CONFIG_OVERRIDE;"` - Then STDERR should contain: - """ - TEST_CONFIG_OVERRIDE - """ - - When I run `WP_CONFIG_PATH=wp-config-override.php wp eval "echo 'TEST_CONFIG_OVERRIDE => ' . TEST_CONFIG_OVERRIDE;"` - Then STDERR should be empty - And STDOUT should contain: - """ - TEST_CONFIG_OVERRIDE => success - """ diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 0e80e58c3..000000000 --- a/manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "requires_php": "7.2.24" -} diff --git a/php/WP_CLI/AutoloadSplitter.php b/php/WP_CLI/AutoloadSplitter.php new file mode 100644 index 000000000..35672cd0d --- /dev/null +++ b/php/WP_CLI/AutoloadSplitter.php @@ -0,0 +1,33 @@ +namespaces[] = [ + $this->namespaces[] = array( 'root' => $this->normalize_root( (string) $root ), 'base_dir' => $this->add_trailing_slash( (string) $base_dir ), 'prefix' => (string) $prefix, 'suffix' => (string) $suffix, 'lowercase' => (bool) $lowercase, 'underscores' => (bool) $underscores, - ]; + ); return $this; } @@ -101,8 +99,7 @@ public function autoload( $class ) { // Remove namespace root level to correspond with root filesystem, and // replace the namespace separator "\" by the system-dependent directory separator. $filename = str_replace( - [ $namespace['root'], '\\' ], - [ '', DIRECTORY_SEPARATOR ], + array( $namespace['root'], '\\' ), array( '', DIRECTORY_SEPARATOR ), $class ); diff --git a/php/WP_CLI/Bootstrap/AutoloaderStep.php b/php/WP_CLI/Bootstrap/AutoloaderStep.php index 286dac777..4818771ed 100644 --- a/php/WP_CLI/Bootstrap/AutoloaderStep.php +++ b/php/WP_CLI/Bootstrap/AutoloaderStep.php @@ -2,9 +2,6 @@ namespace WP_CLI\Bootstrap; -use Exception; -use WP_CLI; - /** * Abstract class AutoloaderStep. * @@ -35,24 +32,17 @@ public function process( BootstrapState $state ) { $autoloader_paths = $this->get_autoloader_paths(); if ( false === $autoloader_paths ) { - // Skip this autoload step. + // Skip this autoloading step. return $state; } foreach ( $autoloader_paths as $autoloader_path ) { if ( is_readable( $autoloader_path ) ) { try { - WP_CLI::debug( - sprintf( - 'Loading detected autoloader: %s', - $autoloader_path - ), - 'bootstrap' - ); require $autoloader_path; $found_autoloader = true; - } catch ( Exception $exception ) { - WP_CLI::warning( + } catch ( \Exception $exception ) { + \WP_CLI::warning( "Failed to load autoloader '{$autoloader_path}'. Reason: " . $exception->getMessage() ); @@ -78,16 +68,7 @@ protected function get_custom_vendor_folder() { return false; } - $contents = file_get_contents( $maybe_composer_json ); - - if ( false === $contents ) { - return false; - } - - /** - * @var object{config: object{'vendor-dir': string}} $composer - */ - $composer = json_decode( $contents ); + $composer = json_decode( file_get_contents( $maybe_composer_json ) ); if ( ! empty( $composer->config ) && ! empty( $composer->config->{'vendor-dir'} ) diff --git a/php/WP_CLI/Bootstrap/BootstrapState.php b/php/WP_CLI/Bootstrap/BootstrapState.php index 1265b641c..bc7def0d0 100644 --- a/php/WP_CLI/Bootstrap/BootstrapState.php +++ b/php/WP_CLI/Bootstrap/BootstrapState.php @@ -8,10 +8,6 @@ * Represents the state that is passed from one bootstrap step to the next. * * @package WP_CLI\Bootstrap - * - * Maintain BC: Changing the method names in this class breaks autoload interactions between Phar - * & framework/commands you use outside of Phar (like when running the Phar WP inside of a command folder). - * @phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid */ class BootstrapState { @@ -28,7 +24,7 @@ class BootstrapState { * * @var array */ - private $state = []; + private $state = array(); /** * Get the state value for a given key. @@ -38,6 +34,7 @@ class BootstrapState { * * @return mixed */ + // @codingStandardsIgnoreLine public function getValue( $key, $fallback = null ) { return array_key_exists( $key, $this->state ) ? $this->state[ $key ] @@ -52,6 +49,7 @@ public function getValue( $key, $fallback = null ) { * * @return void */ + // @codingStandardsIgnoreLine public function setValue( $key, $value ) { $this->state[ $key ] = $value; } diff --git a/php/WP_CLI/Bootstrap/CheckRoot.php b/php/WP_CLI/Bootstrap/CheckRoot.php deleted file mode 100644 index 208dd73e9..000000000 --- a/php/WP_CLI/Bootstrap/CheckRoot.php +++ /dev/null @@ -1,88 +0,0 @@ -getValue( 'config', [] ); - if ( array_key_exists( 'allow-root', $config ) && true === $config['allow-root'] ) { - // They're aware of the risks and set a flag to allow root. - return $state; - } - - if ( getenv( 'WP_CLI_ALLOW_ROOT' ) ) { - // They're aware of the risks and set an environment variable to allow root. - return $state; - } - - /** - * @var string[] $args - */ - $args = $state->getValue( 'arguments', [] ); - if ( count( $args ) >= 2 && 'cli' === $args[0] && in_array( $args[1], [ 'update', 'info' ], true ) ) { - // Make it easier to update root-owned copies. - return $state; - } - - if ( ! function_exists( 'posix_geteuid' ) ) { - // POSIX functions not available. - return $state; - } - - if ( posix_geteuid() !== 0 ) { - // Not root. - return $state; - } - - WP_CLI::error( - "YIKES! It looks like you're running this as root. You probably meant to " . - "run this as the user that your WordPress installation exists under.\n" . - "\n" . - "If you REALLY mean to run this as root, we won't stop you, but just " . - 'bear in mind that any code on this site will then have full control of ' . - "your server, making it quite DANGEROUS.\n" . - "\n" . - "If you'd like to continue as root, please run this again, adding this " . - "flag: --allow-root\n" . - "\n" . - "If you'd like to run it as the user that this site is under, you can " . - "run the following to become the respective user:\n" . - "\n" . - " sudo -u USER -i -- wp \n" . - "\n" . - "(omit -i when using a system user account)\n" . - "\n" . - "Note: When using 'sudo -i', the command is passed via the login " . - "shell's '-c' flag, which strips one layer of quotes. To correctly " . - 'pass empty arguments or arguments with spaces, wrap them in an ' . - "extra set of quotes (e.g. single quotes inside double quotes):\n" . - "\n" . - " sudo -u USER -i -- wp search-replace \"'old'\" \"''\" --path=/var/www/html\n" . - "\n" . - 'The outer double quotes are consumed by the login shell, and the inner ' . - "single quotes are used only for grouping, so WP-CLI receives the intended\n" . - "arguments (including empty and space-containing ones) without the quote\n" . - "characters themselves.\n" - ); - } -} diff --git a/php/WP_CLI/Bootstrap/ConfigureRunner.php b/php/WP_CLI/Bootstrap/ConfigureRunner.php index 6bb65511d..463a29a1c 100644 --- a/php/WP_CLI/Bootstrap/ConfigureRunner.php +++ b/php/WP_CLI/Bootstrap/ConfigureRunner.php @@ -22,10 +22,6 @@ public function process( BootstrapState $state ) { $runner = new RunnerInstance(); $runner()->init_config(); - $state->setValue( 'config', $runner()->config ); - $state->setValue( 'arguments', $runner()->arguments ); - $state->setValue( 'assoc_args', $runner()->assoc_args ); - return $state; } } diff --git a/php/WP_CLI/Bootstrap/DeclareFallbackFunctions.php b/php/WP_CLI/Bootstrap/DeclareFallbackFunctions.php deleted file mode 100644 index 19cd9582f..000000000 --- a/php/WP_CLI/Bootstrap/DeclareFallbackFunctions.php +++ /dev/null @@ -1,25 +0,0 @@ -arguments ); diff --git a/php/WP_CLI/Bootstrap/IncludeBundledAutoloader.php b/php/WP_CLI/Bootstrap/IncludeBundledAutoloader.php new file mode 100644 index 000000000..5d0b6e048 --- /dev/null +++ b/php/WP_CLI/Bootstrap/IncludeBundledAutoloader.php @@ -0,0 +1,37 @@ +get_custom_vendor_folder() ) { + array_unshift( + $autoloader_paths, + WP_CLI_ROOT . '/../../../' . $custom_vendor . '/autoload_commands.php' + ); + } + + return $autoloader_paths; + } +} diff --git a/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php b/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php index 807e4f90f..cccd6faed 100644 --- a/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php +++ b/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php @@ -2,8 +2,6 @@ namespace WP_CLI\Bootstrap; -use WP_CLI; - /** * Class IncludeFallbackAutoloader. * @@ -17,29 +15,21 @@ final class IncludeFallbackAutoloader extends AutoloaderStep { /** * Get the autoloader paths to scan for an autoloader. * - * @return string[] Array of autoloader paths, or an empty array if none are found. + * @return string[]|false Array of strings with autoloader paths, or false + * to skip. */ protected function get_autoloader_paths() { - $autoloader_paths = [ + $autoloader_paths = array( WP_CLI_VENDOR_DIR . '/autoload.php', - ]; + ); - $custom_vendor = $this->get_custom_vendor_folder(); - if ( false !== $custom_vendor ) { + if ( $custom_vendor = $this->get_custom_vendor_folder() ) { array_unshift( $autoloader_paths, WP_CLI_ROOT . '/../../../' . $custom_vendor . '/autoload.php' ); } - WP_CLI::debug( - sprintf( - 'Fallback autoloader paths: %s', - implode( ', ', $autoloader_paths ) - ), - 'bootstrap' - ); - return $autoloader_paths; } } diff --git a/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php b/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php index 4969914f4..b463560bf 100644 --- a/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php +++ b/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php @@ -2,51 +2,49 @@ namespace WP_CLI\Bootstrap; -use WP_CLI\Autoloader; - /** * Class IncludeFrameworkAutoloader. * - * Loads the framework autoloader through an autoloader separate from the - * Composer one, to avoid coupling the loading of the framework with bundled - * commands. + * Loads the framework autoloader that is provided through the `composer.json` + * file. * * This only contains classes for the framework. * * @package WP_CLI\Bootstrap */ -final class IncludeFrameworkAutoloader implements BootstrapStep { +final class IncludeFrameworkAutoloader extends AutoloaderStep { /** - * Process this single bootstrapping step. - * - * @param BootstrapState $state Contextual state to pass into the step. + * Get the autoloader paths to scan for an autoloader. * - * @return BootstrapState Modified state to pass to the next step. + * @return string[]|false Array of strings with autoloader paths, or false + * to skip. */ - public function process( BootstrapState $state ) { - if ( ! class_exists( 'WP_CLI\Autoloader' ) ) { - require_once WP_CLI_ROOT . '/php/WP_CLI/Autoloader.php'; - } - - $autoloader = new Autoloader(); - - $mappings = [ - 'WP_CLI' => WP_CLI_ROOT . '/php/WP_CLI', - 'cli' => WP_CLI_VENDOR_DIR . '/wp-cli/php-cli-tools/lib/cli', - ]; - - foreach ( $mappings as $namespace => $folder ) { - $autoloader->add_namespace( - $namespace, - $folder + protected function get_autoloader_paths() { + $autoloader_paths = array( + WP_CLI_VENDOR_DIR . '/autoload_framework.php', + ); + + if ( $custom_vendor = $this->get_custom_vendor_folder() ) { + array_unshift( + $autoloader_paths, + WP_CLI_ROOT . '/../../../' . $custom_vendor . '/autoload_framework.php' ); } - include_once WP_CLI_VENDOR_DIR . '/wp-cli/mustangostang-spyc/Spyc.php'; - - $autoloader->register( true ); + return $autoloader_paths; + } - return $state; + /** + * Handle the failure to find an autoloader. + * + * @return void + */ + protected function handle_failure() { + fwrite( + STDERR, + "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" + ); + exit( 3 ); } } diff --git a/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php b/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php index 46b6fe48b..141839145 100644 --- a/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php +++ b/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php @@ -2,8 +2,6 @@ namespace WP_CLI\Bootstrap; -use WP_CLI; - /** * Class IncludePackageAutoloader. * @@ -20,14 +18,14 @@ final class IncludePackageAutoloader extends AutoloaderStep { * to skip. */ protected function get_autoloader_paths() { - if ( $this->state->getValue( BootstrapState::IS_PROTECTED_COMMAND, false ) ) { + if ( $this->state->getValue( BootstrapState::IS_PROTECTED_COMMAND, $fallback = false ) ) { return false; } $runner = new RunnerInstance(); $skip_packages = $runner()->config['skip-packages']; if ( true === $skip_packages ) { - WP_CLI::debug( 'Skipped loading packages.', 'bootstrap' ); + \WP_CLI::debug( 'Skipped loading packages.', 'bootstrap' ); return false; } @@ -35,14 +33,14 @@ protected function get_autoloader_paths() { $autoloader_path = $runner()->get_packages_dir_path() . 'vendor/autoload.php'; if ( is_readable( $autoloader_path ) ) { - WP_CLI::debug( + \WP_CLI::debug( 'Loading packages from: ' . $autoloader_path, 'bootstrap' ); - return [ + return array( $autoloader_path, - ]; + ); } return false; @@ -54,6 +52,6 @@ protected function get_autoloader_paths() { * @return void */ protected function handle_failure() { - WP_CLI::debug( 'No package autoload found to load.', 'bootstrap' ); + \WP_CLI::debug( 'No package autoload found to load.', 'bootstrap' ); } } diff --git a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php deleted file mode 100644 index 02826834c..000000000 --- a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php +++ /dev/null @@ -1,137 +0,0 @@ -alias - && isset( $runner()->aliases[ $runner()->alias ]['path'] ) ) { - $alias_path = $runner()->aliases[ $runner()->alias ]['path']; - // Make sure it isn't an invalid value. - if ( is_bool( $alias_path ) || empty( $alias_path ) ) { - return $state; - } - if ( ! Path::is_absolute( $alias_path ) ) { - $alias_path = getcwd() . '/' . $alias_path; - } - $wp_root = rtrim( $alias_path, '/' ); - } else { - // Make sure we don't deal with an invalid `--path` value. - $config = $runner()->config; - if ( isset( $config['path'] ) && - ( is_bool( $config['path'] ) || empty( $config['path'] ) ) - ) { - return $state; - } - $wp_root = rtrim( $runner()->find_wp_root(), '/' ); - } - - // First try to detect a newer Requests version bundled with WordPress. - if ( file_exists( $wp_root . '/wp-includes/Requests/src/Autoload.php' ) ) { - if ( ! class_exists( '\\WpOrg\\Requests\\Autoload', false ) ) { - require_once $wp_root . '/wp-includes/Requests/src/Autoload.php'; - } - - if ( class_exists( '\\WpOrg\\Requests\\Autoload' ) ) { - \WpOrg\Requests\Autoload::register(); - $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CORE ); - return $state; - } - } - - // Then see if we can detect the older version bundled with WordPress. - if ( file_exists( $wp_root . '/wp-includes/class-requests.php' ) ) { - if ( ! class_exists( '\\Requests', false ) ) { - require_once $wp_root . '/wp-includes/class-requests.php'; - } - - if ( class_exists( '\\Requests' ) ) { - // @phpstan-ignore staticMethod.deprecatedClass - \Requests::register_autoloader(); - $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V1, self::FROM_WP_CORE ); - return $state; - } - } - - // Finally, fall back to the Requests version bundled with WP-CLI. - $autoloader = new Autoloader(); - $autoloader->add_namespace( - 'WpOrg\Requests', - WP_CLI_ROOT . '/bundle/rmccue/requests/src' - ); - - $autoloader->register(); - - \WpOrg\Requests\Autoload::register(); - - $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CLI ); - - return $state; - } - - /** - * Store meta information about the used Requests integration. - * - * This can be used for all the conditional code that needs to work - * across multiple Requests versions. - * - * @param string $class_name The class name of the Requests integration. - * @param string $source The source of the Requests integration. - */ - private function store_requests_meta( $class_name, $source ): void { - RequestsLibrary::set_version( - RequestsLibrary::CLASS_NAME_V2 === $class_name - ? RequestsLibrary::VERSION_V2 - : RequestsLibrary::VERSION_V1 - ); - RequestsLibrary::set_source( $source ); - RequestsLibrary::set_class_name( $class_name ); - } -} diff --git a/php/WP_CLI/Bootstrap/InitializeContexts.php b/php/WP_CLI/Bootstrap/InitializeContexts.php deleted file mode 100644 index aff7539b3..000000000 --- a/php/WP_CLI/Bootstrap/InitializeContexts.php +++ /dev/null @@ -1,46 +0,0 @@ - new Context\Cli(), - Context::ADMIN => new Context\Admin(), - Context::FRONTEND => new Context\Frontend(), - Context::AUTO => new Context\Auto( $context_manager ), - ]; - - /** - * @var array $contexts - */ - $contexts = WP_CLI::do_hook( 'before_registering_contexts', $contexts ); - - foreach ( $contexts as $name => $implementation ) { - $context_manager->register_context( $name, $implementation ); - } - - $state->setValue( 'context_manager', $context_manager ); - - return $state; - } -} diff --git a/php/WP_CLI/Bootstrap/InitializeLogger.php b/php/WP_CLI/Bootstrap/InitializeLogger.php index f157da067..9915b37d3 100644 --- a/php/WP_CLI/Bootstrap/InitializeLogger.php +++ b/php/WP_CLI/Bootstrap/InitializeLogger.php @@ -2,8 +2,6 @@ namespace WP_CLI\Bootstrap; -use DirectoryIterator; - /** * Class InitializeLogger. * @@ -31,9 +29,9 @@ public function process( BootstrapState $state ) { /** * Load the class declarations for the loggers. */ - private function declare_loggers(): void { + private function declare_loggers() { $logger_dir = WP_CLI_ROOT . '/php/WP_CLI/Loggers'; - $iterator = new DirectoryIterator( $logger_dir ); + $iterator = new \DirectoryIterator( $logger_dir ); // Make sure the base class is declared first. include_once "$logger_dir/Base.php"; diff --git a/php/WP_CLI/Bootstrap/LaunchRunner.php b/php/WP_CLI/Bootstrap/LaunchRunner.php index 77f8ce9f8..d85831db1 100644 --- a/php/WP_CLI/Bootstrap/LaunchRunner.php +++ b/php/WP_CLI/Bootstrap/LaunchRunner.php @@ -20,14 +20,6 @@ final class LaunchRunner implements BootstrapStep { */ public function process( BootstrapState $state ) { $runner = new RunnerInstance(); - - /** - * @var \WP_CLI\ContextManager $context_manager - */ - $context_manager = $state->getValue( 'context_manager' ); - - $runner()->register_context_manager( $context_manager ); - $runner()->start(); return $state; diff --git a/php/WP_CLI/Bootstrap/InitializeFormatter.php b/php/WP_CLI/Bootstrap/LoadDispatcher.php similarity index 63% rename from php/WP_CLI/Bootstrap/InitializeFormatter.php rename to php/WP_CLI/Bootstrap/LoadDispatcher.php index cf0cd3513..48aa0d08c 100644 --- a/php/WP_CLI/Bootstrap/InitializeFormatter.php +++ b/php/WP_CLI/Bootstrap/LoadDispatcher.php @@ -3,13 +3,13 @@ namespace WP_CLI\Bootstrap; /** - * Class InitializeFormatter. + * Class LoadDispatcher. * - * Registers the built-in format handlers for the Formatter class. + * Loads the dispatcher that will dispatch command names to file locations. * * @package WP_CLI\Bootstrap */ -final class InitializeFormatter implements BootstrapStep { +final class LoadDispatcher implements BootstrapStep { /** * Process this single bootstrapping step. @@ -19,7 +19,7 @@ final class InitializeFormatter implements BootstrapStep { * @return BootstrapState Modified state to pass to the next step. */ public function process( BootstrapState $state ) { - \WP_CLI\Formatter::register_builtin_formats(); + require_once WP_CLI_ROOT . '/php/dispatcher.php'; return $state; } diff --git a/php/WP_CLI/Bootstrap/LoadExecCommand.php b/php/WP_CLI/Bootstrap/LoadExecCommand.php deleted file mode 100644 index 5983f2206..000000000 --- a/php/WP_CLI/Bootstrap/LoadExecCommand.php +++ /dev/null @@ -1,39 +0,0 @@ -` option. - * - * @package WP_CLI\Bootstrap - */ -final class LoadExecCommand implements BootstrapStep { - - /** - * Process this single bootstrapping step. - * - * @param BootstrapState $state Contextual state to pass into the step. - * - * @return BootstrapState Modified state to pass to the next step. - */ - public function process( BootstrapState $state ) { - if ( $state->getValue( BootstrapState::IS_PROTECTED_COMMAND, false ) ) { - return $state; - } - - $runner = new RunnerInstance(); - if ( ! isset( $runner()->config['exec'] ) ) { - return $state; - } - - foreach ( $runner()->config['exec'] as $php_code ) { - eval( $php_code ); // phpcs:ignore Squiz.PHP.Eval.Discouraged - } - - return $state; - } -} diff --git a/php/WP_CLI/Bootstrap/LoadRequiredCommand.php b/php/WP_CLI/Bootstrap/LoadRequiredCommand.php index e4347fefc..db1230b96 100644 --- a/php/WP_CLI/Bootstrap/LoadRequiredCommand.php +++ b/php/WP_CLI/Bootstrap/LoadRequiredCommand.php @@ -3,7 +3,6 @@ namespace WP_CLI\Bootstrap; use WP_CLI; -use WP_CLI\Path; use WP_CLI\Utils; /** @@ -23,7 +22,7 @@ final class LoadRequiredCommand implements BootstrapStep { * @return BootstrapState Modified state to pass to the next step. */ public function process( BootstrapState $state ) { - if ( $state->getValue( BootstrapState::IS_PROTECTED_COMMAND, false ) ) { + if ( $state->getValue( BootstrapState::IS_PROTECTED_COMMAND, $fallback = false ) ) { return $state; } @@ -34,19 +33,16 @@ public function process( BootstrapState $state ) { foreach ( $runner()->config['require'] as $path ) { if ( ! file_exists( $path ) ) { - $context = ''; + $context = ''; $required_files = $runner()->get_required_files(); - foreach ( [ 'system', 'global', 'project', 'runtime' ] as $scope ) { - if ( isset( $required_files[ $scope ] ) && in_array( $path, $required_files[ $scope ], true ) ) { + foreach ( array( 'global', 'project', 'runtime' ) as $scope ) { + if ( in_array( $path, $required_files[ $scope ], true ) ) { switch ( $scope ) { - case 'system': - $context = ' (from system ' . Path::basename( (string) $runner()->get_system_config_path() ) . ')'; - break; case 'global': - $context = ' (from global ' . Path::basename( (string) $runner()->get_global_config_path() ) . ')'; + $context = ' (from global ' . Utils\basename( $runner()->get_global_config_path() ) . ')'; break; case 'project': - $context = ' (from project\'s ' . Path::basename( (string) $runner()->get_project_config_path() ) . ')'; + $context = ' (from project\'s ' . Utils\basename( $runner()->get_project_config_path() ) . ')'; break; case 'runtime': $context = ' (from runtime argument)'; @@ -55,7 +51,7 @@ public function process( BootstrapState $state ) { break; } } - WP_CLI::error( sprintf( "Required file '%s' doesn't exist%s.", Path::basename( $path ), $context ) ); + WP_CLI::error( sprintf( "Required file '%s' doesn't exist%s.", Utils\basename( $path ), $context ) ); } Utils\load_file( $path ); WP_CLI::debug( 'Required file from config: ' . $path, 'bootstrap' ); diff --git a/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php b/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php index a9b0b01d2..185eb61c5 100644 --- a/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php +++ b/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php @@ -20,7 +20,6 @@ final class LoadUtilityFunctions implements BootstrapStep { */ public function process( BootstrapState $state ) { require_once WP_CLI_ROOT . '/php/utils.php'; - require_once WP_CLI_ROOT . '/php/dispatcher.php'; return $state; } diff --git a/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php b/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php index b368bfed2..044b954a5 100644 --- a/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php +++ b/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php @@ -2,9 +2,6 @@ namespace WP_CLI\Bootstrap; -use WP_CLI; -use WP_CLI\Utils; - /** * Class RegisterDeferredCommands. * @@ -30,9 +27,9 @@ public function process( BootstrapState $state ) { // Process deferred command additions for commands added through // plugins. - WP_CLI::add_hook( - 'before_run_command', - [ $this, 'add_deferred_commands' ] + \WP_CLI::add_hook( + 'find_command_to_run_pre', + array( $this, 'add_deferred_commands' ) ); return $state; @@ -42,37 +39,10 @@ public function process( BootstrapState $state ) { * Add deferred commands that are still waiting to be processed. */ public function add_deferred_commands() { - $deferred_additions = WP_CLI::get_deferred_additions(); + $deferred_additions = \WP_CLI::get_deferred_additions(); foreach ( $deferred_additions as $name => $addition ) { - $addition_data = []; - foreach ( $addition as $addition_key => $addition_value ) { - // Describe the callable as a string instead of directly printing it - // for better debug info. - if ( 'callable' === $addition_key ) { - $addition_value = Utils\describe_callable( $addition_value ); - - } elseif ( is_array( $addition_value ) ) { - $addition_value = json_encode( $addition_value ); - } - - $addition_data[] = sprintf( - '%s: %s', - $addition_key, - $addition_value - ); - } - - WP_CLI::debug( - sprintf( - 'Adding deferred command: %s (%s)', - $name, - implode( ', ', $addition_data ) - ), - 'bootstrap' - ); - - WP_CLI::add_command( + \WP_CLI::add_command( $name, $addition['callable'], $addition['args'] diff --git a/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php b/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php index 29881c8cf..2020f3fdd 100644 --- a/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php +++ b/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php @@ -2,10 +2,6 @@ namespace WP_CLI\Bootstrap; -use DirectoryIterator; -use Exception; -use WP_CLI; - /** * Class RegisterFrameworkCommands. * @@ -25,7 +21,7 @@ final class RegisterFrameworkCommands implements BootstrapStep { public function process( BootstrapState $state ) { $cmd_dir = WP_CLI_ROOT . '/php/commands'; - $iterator = new DirectoryIterator( $cmd_dir ); + $iterator = new \DirectoryIterator( $cmd_dir ); foreach ( $iterator as $filename ) { if ( '.php' !== substr( $filename, - 4 ) ) { @@ -33,17 +29,9 @@ public function process( BootstrapState $state ) { } try { - WP_CLI::debug( - sprintf( - 'Adding framework command: %s', - "$cmd_dir/$filename" - ), - 'bootstrap' - ); - include_once "$cmd_dir/$filename"; - } catch ( Exception $exception ) { - WP_CLI::warning( + } catch ( \Exception $exception ) { + \WP_CLI::warning( "Could not add command {$cmd_dir}/{$filename}. Reason: " . $exception->getMessage() ); } diff --git a/php/WP_CLI/Bootstrap/RegisterShutdownHandler.php b/php/WP_CLI/Bootstrap/RegisterShutdownHandler.php deleted file mode 100644 index 05e031af1..000000000 --- a/php/WP_CLI/Bootstrap/RegisterShutdownHandler.php +++ /dev/null @@ -1,28 +0,0 @@ -words = explode( ' ', $line ); - // First word is always `wp`. + // first word is always `wp` array_shift( $this->words ); - // Last word is either empty or an incomplete subcommand. - $this->cur_word = (string) end( $this->words ); + // last word is either empty or an incomplete subcommand + $this->cur_word = end( $this->words ); if ( '' !== $this->cur_word && ! preg_match( '/^\-/', $this->cur_word ) ) { array_pop( $this->words ); } - // Last word is an incomplete `--url` parameter. - if ( 0 === strpos( $this->cur_word, '--url=' ) ) { - $parameter = explode( '=', $this->cur_word, 2 ); - $this->cur_word = isset( $parameter[1] ) ? $parameter[1] : ''; - $urls = $this->get_network_urls(); - - // So we can remove the network URL from the subdirectory. - $home_url = $this->get_network_home_url(); - $home_url_no_scheme = (string) preg_replace( '#^https?://#', '', $home_url ); - - foreach ( $urls as $url ) { - $this->add( $url ); - $url_no_scheme = (string) preg_replace( '#^https?://#', '', $url ); - if ( $url_no_scheme !== $url ) { - $this->add( rtrim( $url_no_scheme, '/\\' ) ); - } - - $url_no_home = str_replace( Path::trailingslashit( $home_url_no_scheme ), '', $url_no_scheme ); - if ( $url_no_home !== $url ) { - $this->add( rtrim( $url_no_home, '/\\' ) ); - } - } - - return; - } - $is_alias = false; - $is_help = false; + $is_help = false; if ( ! empty( $this->words[0] ) && preg_match( '/^@/', $this->words[0] ) ) { array_shift( $this->words ); // `wp @al` is false, but `wp @all ` is true. @@ -100,20 +49,12 @@ public function __construct( $line ) { } } - // Check if we're trying to complete a flag value (e.g., --format=) - if ( preg_match( '/^--([a-z-_0-9]+)=(.*)$/i', $this->cur_word, $matches ) ) { - $param_name = $matches[1]; - $param_value = $matches[2]; - $this->add_param_values( $command, $param_name, $param_value ); - return; - } - if ( $command->can_have_subcommands() ) { - // Add completion when command is `wp` and alias isn't set. - if ( 'wp' === $command->get_name() && false === $is_alias && false === $is_help ) { - $aliases = WP_CLI::get_configurator()->get_aliases(); + // add completion when command is `wp` and alias isn't set. + if ( 'wp' === $command->get_name() && false === $is_alias && false == $is_help ) { + $aliases = \WP_CLI::get_configurator()->get_aliases(); foreach ( $aliases as $name => $_ ) { - $this->add( "@$name " ); + $this->add( "$name " ); } } foreach ( $command->get_subcommands() as $name => $_ ) { @@ -121,7 +62,7 @@ public function __construct( $line ) { } } else { foreach ( $spec as $arg ) { - if ( in_array( $arg['type'], [ 'flag', 'assoc' ], true ) ) { + if ( in_array( $arg['type'], array( 'flag', 'assoc' ) ) ) { if ( isset( $assoc_args[ $arg['name'] ] ) ) { continue; } @@ -154,59 +95,37 @@ public function __construct( $line ) { $this->add( $opt ); } } + } - /** - * Get the specific WP-CLI command that is being referenced. - * - * @param array $words Individual input line words. - * - * @return array{0: \WP_CLI\Dispatcher\CompositeCommand, 1: array, 2: array}|string Array with command, args, and assoc_args on success; error string on failure. - */ private function get_command( $words ) { - $positional_args = []; - $assoc_args = []; - - # Avoid having to polyfill array_key_last(). - end( $words ); - $last_arg_i = key( $words ); - foreach ( $words as $i => $arg ) { - if ( preg_match( '|^--([^=]+)(=?)|', $arg, $matches ) ) { - if ( $i === $last_arg_i && '' === $matches[2] ) { - continue; - } + $positional_args = $assoc_args = array(); + + foreach ( $words as $arg ) { + if ( preg_match( '|^--([^=]+)=?|', $arg, $matches ) ) { $assoc_args[ $matches[1] ] = true; } else { $positional_args[] = $arg; } } - $r = WP_CLI::get_runner()->find_command_to_run( $positional_args ); - if ( ! is_array( $r ) && array_pop( $positional_args ) === $this->cur_word ) { - $r = WP_CLI::get_runner()->find_command_to_run( $positional_args ); + $r = \WP_CLI::get_runner()->find_command_to_run( $positional_args ); + if ( ! is_array( $r ) && array_pop( $positional_args ) == $this->cur_word ) { + $r = \WP_CLI::get_runner()->find_command_to_run( $positional_args ); } - /** - * @var array{0: \WP_CLI\Dispatcher\CompositeCommand, 1: array, 2: array}|string $r - */ - if ( ! is_array( $r ) ) { return $r; } list( $command, $args ) = $r; - return [ $command, $args, $assoc_args ]; + return array( $command, $args, $assoc_args ); } - /** - * Get global parameters. - * - * @return array Associative array of global parameters. - */ private function get_global_parameters() { - $params = []; - foreach ( WP_CLI::get_configurator()->get_spec() as $key => $details ) { + $params = array(); + foreach ( \WP_CLI::get_configurator()->get_spec() as $key => $details ) { if ( false === $details['runtime'] ) { continue; } @@ -229,119 +148,7 @@ private function get_global_parameters() { return $params; } - /** - * Get the current network's home URL. - * - * @return string Home URL. - */ - private function get_network_home_url() { - $cache = WP_CLI::get_cache(); - - // Use the WP root to key the cache, so we don't mix results from different projects. - $wp_root = WP_CLI::get_runner()->find_wp_root(); - $cache_key = sprintf( 'network-home:%s', md5( $wp_root ) ); - - $result = $cache->read( $cache_key, 300 ); // 5 minutes TTL - - if ( false === $result ) { - $result = WP_CLI::runcommand( - 'option get home', - [ - 'return' => 'stdout', - ] - ); - - $cache->write( $cache_key, $result ); - } - - return $result; - } - - /** - * Get URLs in the Multisite network matching the input. - * - * @return string[] All of the URLs. - */ - private function get_network_urls() { - $cache = WP_CLI::get_cache(); - - // Use the WP root to key the cache, so we don't mix results from different projects. - $wp_root = WP_CLI::get_runner()->find_wp_root(); - $cache_key = sprintf( 'network-urls:%s', md5( $wp_root ) ); - - $result = $cache->read( $cache_key, 300 ); // 5 minutes TTL - - if ( false === $result ) { - $result = WP_CLI::runcommand( - 'site list', - [ - 'return' => 'stdout', - 'command_args' => [ '--field=url', '--number=-1', '--format=json' ], - ] - ); - - $cache->write( $cache_key, $result ); - } - - /** - * @var string[]|null $urls - */ - $urls = json_decode( (string) $result, true ); - - return is_array( $urls ) ? $urls : []; - } - - /** - * Add parameter values to completions if the parameter has defined options. - * - * Extracts enum options from the command's PHPdoc YAML blocks using DocParser. - * If options are found, they are filtered by the partial value and added to completions. - * - * @param \WP_CLI\Dispatcher\CompositeCommand $command Command object. - * @param string $param_name Parameter name. - * @param string $param_value Current partial value. - */ - private function add_param_values( $command, $param_name, $param_value ) { - $options = []; - - // First, try to get options from the command's documentation - $longdesc = $command->get_longdesc(); - if ( $longdesc ) { - $parser = new DocParser( $longdesc ); - $param_args = $parser->get_param_args( $param_name ); - - if ( $param_args && isset( $param_args['options'] ) ) { - $options = $param_args['options']; - } - } - - // If no options found in command doc, check global parameters - if ( empty( $options ) ) { - $global_params = WP_CLI::get_configurator()->get_spec(); - if ( isset( $global_params[ $param_name ]['enum'] ) ) { - $options = $global_params[ $param_name ]['enum']; - } - } - - if ( empty( $options ) ) { - return; - } - - // Add each option as a completion - foreach ( $options as $option ) { - // Check if the option matches the current partial value - if ( '' === $param_value || 0 === strpos( (string) $option, $param_value ) ) { - $this->opts[] = $option . ' '; - } - } - } - - /** - * Store individual option. - * - * @param string $opt Option to store. - */ - private function add( $opt ): void { + private function add( $opt ) { if ( '' !== $this->cur_word ) { if ( 0 !== strpos( $opt, $this->cur_word ) ) { return; @@ -351,14 +158,9 @@ private function add( $opt ): void { $this->opts[] = $opt; } - /** - * Render the stored options. - * - * @return void - */ public function render() { foreach ( $this->opts as $opt ) { - WP_CLI::line( $opt ); + \WP_CLI::line( $opt ); } } } diff --git a/php/WP_CLI/ComposerIO.php b/php/WP_CLI/ComposerIO.php index 12e703eb4..91bb6c0ba 100644 --- a/php/WP_CLI/ComposerIO.php +++ b/php/WP_CLI/ComposerIO.php @@ -2,8 +2,8 @@ namespace WP_CLI; -use Composer\IO\NullIO; -use WP_CLI; +use \Composer\IO\NullIO; +use \WP_CLI; /** * A Composer IO class so we can provide some level of interactivity from WP-CLI @@ -31,11 +31,9 @@ public function writeError( $messages, $newline = true, $verbosity = self::NORMA self::output_clean_message( $messages ); } - private static function output_clean_message( $messages ) { - $messages = (array) preg_replace( '#<(https?)([^>]+)>#', '$1$2', $messages ); - foreach ( $messages as $message ) { - // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags - WP_CLI::log( strip_tags( trim( $message ) ) ); - } + private static function output_clean_message( $message ) { + $message = preg_replace( '#<(https?)([^>]+)>#', '$1$2', $message ); + WP_CLI::log( strip_tags( trim( $message ) ) ); } + } diff --git a/php/WP_CLI/Configurator.php b/php/WP_CLI/Configurator.php index 8dc2975de..d099eddec 100644 --- a/php/WP_CLI/Configurator.php +++ b/php/WP_CLI/Configurator.php @@ -3,7 +3,6 @@ namespace WP_CLI; use Mustangostang\Spyc; -use SplFileInfo; /** * Handles file- and runtime-based configuration values. @@ -13,163 +12,60 @@ class Configurator { /** - * Configurator argument specification. - * - * @var array + * @var array $spec Configurator argument specification. */ private $spec; /** - * Values for keys defined in Configurator spec. - * - * @var array - */ - private $config = []; - - /** - * Extra config values not specified in spec. - * - * @var array + * @var array $config Values for keys defined in Configurator spec. */ - private $extra_config = []; + private $config = array(); /** - * Any aliases defined in config files. - * - * @var array + * @var array $extra_config Extra config values not specified in spec. */ - private $aliases = []; + private $extra_config = array(); /** - * Raw aliases without environment variable interpolation. - * - * @var array + * @var array $aliases Any aliases defined in config files. */ - private $raw_aliases = []; + private $aliases = array(); /** - * Regex pattern used to define an alias. - * - * @var string + * @var string ALIAS_REGEX Regex pattern used to define an alias */ const ALIAS_REGEX = '^@[A-Za-z0-9-_\.\-]+$'; /** - * Arguments that can be used in an alias. - * - * @var array + * @var array ALIAS_SPEC Arguments that can be used in an alias */ - private static $alias_spec = [ + private static $alias_spec = array( 'user', 'url', 'path', 'ssh', - 'ssh-args', 'http', - 'proxyjump', - 'key', - 'ssh_config', - ]; + ); /** * @param string $path Path to config spec file. */ public function __construct( $path ) { - $this->load_config_spec( $path ); + $this->spec = include $path; - $defaults = [ - 'runtime' => false, - 'file' => false, + $defaults = array( + 'runtime' => false, + 'file' => false, 'synopsis' => '', - 'default' => null, + 'default' => null, 'multiple' => false, - ]; + ); foreach ( $this->spec as $key => &$details ) { $details = array_merge( $defaults, $details ); $this->config[ $key ] = $details['default']; } - - $env_files = getenv( 'WP_CLI_REQUIRE' ) - ? array_filter( array_map( 'trim', explode( ',', (string) getenv( 'WP_CLI_REQUIRE' ) ) ) ) - : []; - - if ( ! empty( $env_files ) ) { - if ( ! isset( $this->config['require'] ) ) { - $this->config['require'] = []; - } - $this->config['require'] = array_unique( array_merge( $env_files, $this->config['require'] ) ); - } - } - - /** - * Loads the config spec file. - * - * @param string $path Path to the config spec file. - */ - private function load_config_spec( $path ) { - $config_spec = include $path; - // A way for platforms to modify $config_spec. - // Use with caution! - $config_spec_filter_callback = defined( 'WP_CLI_CONFIG_SPEC_FILTER_CALLBACK' ) ? constant( 'WP_CLI_CONFIG_SPEC_FILTER_CALLBACK' ) : false; - if ( $config_spec_filter_callback && is_callable( $config_spec_filter_callback ) ) { - $config_spec = $config_spec_filter_callback( $config_spec ); - } - $this->spec = $config_spec; - } - - /** - * Add the given alias to the internal aliases array. - * - * @param string $key The alias name (with or without @ prefix). - * @param array $value The alias configuration. - * @param string $yml_file_dir The directory of the YAML file for path resolution. - * @return void - */ - private function add_alias( $key, $value, $yml_file_dir ) { - if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { - // Remove the @ character from the alias name - $key = substr( $key, 1 ); - } - - $this->aliases[ $key ] = []; - $this->raw_aliases[ $key ] = []; - $is_alias = false; - foreach ( self::$alias_spec as $i ) { - if ( isset( $value[ $i ] ) ) { - // Store raw value before interpolation. - $this->raw_aliases[ $key ][ $i ] = $value[ $i ]; - - // Interpolate environment variables in alias values. - $value[ $i ] = self::interpolate_env_vars( $value[ $i ] ); - if ( 'path' === $i && ! isset( $value['ssh'] ) ) { - self::absolutize( $value[ $i ], $yml_file_dir ); - $value[ $i ] = Path::normalize( $value[ $i ] ); - } - $this->aliases[ $key ][ $i ] = $value[ $i ]; - $is_alias = true; - } - } - - // If it's not an alias, it might be a group of aliases. - if ( ! $is_alias && is_array( $value ) ) { - /** - * @var list $value - */ - $alias_group = []; - foreach ( $value as $k ) { - if ( preg_match( '#' . self::ALIAS_REGEX . '#', $k ) ) { - // Remove the @ character from the alias name - $alias_group[] = substr( $k, 1 ); - } elseif ( array_key_exists( $k, $this->aliases ) ) { - // Check if the alias has been properly declared before adding it to the group - $alias_group[] = $k; - } - } - $this->aliases[ $key ] = $alias_group; - $this->raw_aliases[ $key ] = $alias_group; - } } /** @@ -178,7 +74,7 @@ private function add_alias( $key, $value, $yml_file_dir ) { * @return array */ public function to_array() { - return [ $this->config, $this->extra_config ]; + return array( $this->config, $this->extra_config ); } /** @@ -196,115 +92,54 @@ public function get_spec() { * @return array */ public function get_aliases() { - $runtime_aliases = $this->get_runtime_aliases( true ); - if ( null !== $runtime_aliases ) { - return $runtime_aliases; - } - - return $this->aliases; - } - - /** - * Get raw aliases without environment variable interpolation. - * - * @return array - */ - public function get_raw_aliases() { - $runtime_aliases = $this->get_runtime_aliases( false ); - if ( null !== $runtime_aliases ) { - return $runtime_aliases; - } - - return $this->raw_aliases; - } - - /** - * Get runtime aliases from environment variable. - * - * @param bool $interpolate Whether to interpolate environment variables. - * @return array|null Returns aliases array if runtime alias is set, null otherwise. - */ - private function get_runtime_aliases( $interpolate ) { - $runtime_alias = getenv( 'WP_CLI_RUNTIME_ALIAS' ); - if ( false === $runtime_alias ) { - return null; - } - - $returned_aliases = []; - - /** - * @var string $key - * @var array $value - */ - foreach ( (array) json_decode( $runtime_alias, true ) as $key => $value ) { - if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { - $normalized_key = substr( $key, 1 ); - $returned_aliases[ $normalized_key ] = []; - foreach ( self::$alias_spec as $i ) { - if ( isset( $value[ $i ] ) ) { - $returned_aliases[ $normalized_key ][ $i ] = $interpolate - ? self::interpolate_env_vars( $value[ $i ] ) - : $value[ $i ]; + if ( $runtime_alias = getenv( 'WP_CLI_RUNTIME_ALIAS' ) ) { + $returned_aliases = array(); + foreach ( json_decode( $runtime_alias, true ) as $key => $value ) { + if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { + $returned_aliases[ $key ] = array(); + foreach ( self::$alias_spec as $i ) { + if ( isset( $value[ $i ] ) ) { + $returned_aliases[ $key ][ $i ] = $value[ $i ]; + } } } } + return $returned_aliases; } - return $returned_aliases; + return $this->aliases; } /** * Splits a list of arguments into positional, associative and config. * - * @param array $arguments - * @return array{0: array, 1: array, 2: array|int|string|true>} + * @param array(string) + * @return array(array) */ public function parse_args( $arguments ) { list( $positional_args, $mixed_args, $global_assoc, $local_assoc ) = self::extract_assoc( $arguments ); - list( $assoc_args, $runtime_config ) = $this->unmix_assoc_args( $mixed_args, $global_assoc, $local_assoc ); - return [ $positional_args, $assoc_args, $runtime_config ]; + list( $assoc_args, $runtime_config ) = $this->unmix_assoc_args( $mixed_args, $global_assoc, $local_assoc ); + return array( $positional_args, $assoc_args, $runtime_config ); } /** * Splits positional args from associative args. * - * @param array $arguments - * @return array{0: array, 1: array, 2: array, 3: array} + * @param array + * @return array(array) */ public static function extract_assoc( $arguments ) { - $positional_args = []; - $assoc_args = []; - $global_assoc = []; - $local_assoc = []; - $end_of_options = false; + $positional_args = $assoc_args = $global_assoc = $local_assoc = array(); foreach ( $arguments as $arg ) { - $positional = null; - $assoc_arg = null; - - // Check for the `--` delimiter indicating end of options. - if ( '--' === $arg ) { - $end_of_options = true; - continue; - } + $positional_arg = $assoc_arg = null; - // After `--`, treat all arguments as positional. - if ( $end_of_options ) { - $positional = $arg; - } elseif ( preg_match( '|^--no-([^=]+)$|', $arg, $matches ) ) { - $assoc_arg = [ $matches[1], false ]; + if ( preg_match( '|^--no-([^=]+)$|', $arg, $matches ) ) { + $assoc_arg = array( $matches[1], false ); } elseif ( preg_match( '|^--([^=]+)$|', $arg, $matches ) ) { - $assoc_arg = [ $matches[1], true ]; + $assoc_arg = array( $matches[1], true ); } elseif ( preg_match( '|^--([^=]+)=(.*)|s', $arg, $matches ) ) { - $assoc_arg = [ $matches[1], $matches[2] ]; - } elseif ( preg_match( '|^-([a-zA-Z])$|', $arg, $matches ) ) { - // Support single-dash single-letter short arguments (e.g., -w, -v) - // Note: Only single letters are supported to follow common CLI conventions - // Multi-character aliases should use double-dash (e.g., --debug not -debug) - $assoc_arg = [ $matches[1], true ]; - } elseif ( preg_match( '|^-([a-zA-Z])=(.*)|s', $arg, $matches ) ) { - // Support single-dash single-letter short arguments with values (e.g., -n=5) - $assoc_arg = [ $matches[1], $matches[2] ]; + $assoc_arg = array( $matches[1], $matches[2] ); } else { $positional = $arg; } @@ -321,67 +156,41 @@ public static function extract_assoc( $arguments ) { } } - return [ $positional_args, $assoc_args, $global_assoc, $local_assoc ]; + return array( $positional_args, $assoc_args, $global_assoc, $local_assoc ); } /** * Separate runtime parameters from command-specific parameters. * * @param array $mixed_args - * @return array{0: array, 1: array|int|string|true>} + * @return array */ - private function unmix_assoc_args( $mixed_args, $global_assoc = [], $local_assoc = [] ) { - $assoc_args = []; - $runtime_config = []; + private function unmix_assoc_args( $mixed_args, $global_assoc = array(), $local_assoc = array() ) { + $assoc_args = $runtime_config = array(); if ( getenv( 'WP_CLI_STRICT_ARGS_MODE' ) ) { foreach ( $global_assoc as $tmp ) { - [ $key, $value ] = $tmp; + list( $key, $value ) = $tmp; if ( isset( $this->spec[ $key ] ) && false !== $this->spec[ $key ]['runtime'] ) { $this->assoc_arg_to_runtime_config( $key, $value, $runtime_config ); } } foreach ( $local_assoc as $tmp ) { - [ $key, $value ] = $tmp; - // Collect multiple values for the same key into an array, except for boolean flags - if ( isset( $assoc_args[ $key ] ) ) { - // Boolean flags (--flag or --no-flag) use last-wins behavior - if ( is_bool( $value ) ) { - $assoc_args[ $key ] = $value; - } else { - if ( ! is_array( $assoc_args[ $key ] ) ) { - $assoc_args[ $key ] = [ $assoc_args[ $key ] ]; - } - $assoc_args[ $key ][] = $value; - } - } else { - $assoc_args[ $key ] = $value; - } + $assoc_args[ $tmp[0] ] = $tmp[1]; } } else { foreach ( $mixed_args as $tmp ) { - [ $key, $value ] = $tmp; + list( $key, $value ) = $tmp; if ( isset( $this->spec[ $key ] ) && false !== $this->spec[ $key ]['runtime'] ) { $this->assoc_arg_to_runtime_config( $key, $value, $runtime_config ); - } elseif ( isset( $assoc_args[ $key ] ) ) { - // Collect multiple values for the same key into an array, except for boolean flags - // Boolean flags (--flag or --no-flag) use last-wins behavior - if ( is_bool( $value ) ) { - $assoc_args[ $key ] = $value; - } else { - if ( ! is_array( $assoc_args[ $key ] ) ) { - $assoc_args[ $key ] = [ $assoc_args[ $key ] ]; - } - $assoc_args[ $key ][] = $value; - } } else { $assoc_args[ $key ] = $value; } } } - return [ $assoc_args, $runtime_config ]; + return array( $assoc_args, $runtime_config ); } /** @@ -408,20 +217,32 @@ private function assoc_arg_to_runtime_config( $key, $value, &$runtime_config ) { public function merge_yml( $path, $current_alias = null ) { $yaml = self::load_yml( $path ); if ( ! empty( $yaml['_']['inherit'] ) ) { - $inherit_path = Path::is_absolute( $yaml['_']['inherit'] ) - ? $yaml['_']['inherit'] - : ( new SplFileInfo( Path::normalize( dirname( $path ) . '/' . $yaml['_']['inherit'] ) ) )->getRealPath(); - - $this->merge_yml( $inherit_path, $current_alias ); + $this->merge_yml( $yaml['_']['inherit'], $current_alias ); } - // Prepare the base path for absolutized alias paths. - $yml_file_dir = $path ? dirname( $path ) : ''; + // Prepare the base path for absolutized alias paths + $yml_file_dir = $path ? dirname( $path ) : false; foreach ( $yaml as $key => $value ) { if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) { - $this->add_alias( $key, $value, $yml_file_dir ); - } elseif ( 'aliases' === $key ) { - foreach ( $value as $alias => $alias_config ) { - $this->add_alias( $alias, $alias_config, $yml_file_dir ); + $this->aliases[ $key ] = array(); + $is_alias = false; + foreach ( self::$alias_spec as $i ) { + if ( isset( $value[ $i ] ) ) { + if ( 'path' === $i && ! isset( $value['ssh'] ) ) { + self::absolutize( $value[ $i ], $yml_file_dir ); + } + $this->aliases[ $key ][ $i ] = $value[ $i ]; + $is_alias = true; + } + } + // If it's not an alias, it might be a group of aliases + if ( ! $is_alias && is_array( $value ) ) { + $alias_group = array(); + foreach ( $value as $i => $k ) { + if ( preg_match( '#' . self::ALIAS_REGEX . '#', $k ) ) { + $alias_group[] = $k; + } + } + $this->aliases[ $key ] = $alias_group; } } elseif ( ! isset( $this->spec[ $key ] ) || false === $this->spec[ $key ]['file'] ) { if ( isset( $this->extra_config[ $key ] ) @@ -454,8 +275,8 @@ public function merge_array( $config ) { if ( false !== $details['runtime'] && isset( $config[ $key ] ) ) { $value = $config[ $key ]; - if ( 'require' === $key ) { - $value = Utils\expand_globs( $value ); + if ( 'require' == $key ) { + $value = \WP_CLI\Utils\expand_globs( $value ); } if ( $details['multiple'] ) { @@ -472,11 +293,11 @@ public function merge_array( $config ) { * Load values from a YAML file. * * @param string $yml_file Path to the YAML file - * @return array Declared configuration values + * @return array $config Declared configuration values */ private static function load_yml( $yml_file ) { if ( ! $yml_file ) { - return []; + return array(); } $config = Spyc::YAMLLoad( $yml_file ); @@ -490,28 +311,22 @@ private static function load_yml( $yml_file ) { if ( isset( $config['require'] ) ) { self::arrayify( $config['require'] ); - // @phpstan-ignore argument.type - $config['require'] = Utils\expand_globs( array_map( 'strval', $config['require'] ) ); + $config['require'] = \WP_CLI\Utils\expand_globs( $config['require'] ); foreach ( $config['require'] as &$path ) { self::absolutize( $path, $yml_file_dir ); } } // Backwards compat - // Command 'core config' was moved to 'config create'. + // 'core config' -> 'config create' if ( isset( $config['core config'] ) ) { $config['config create'] = $config['core config']; unset( $config['core config'] ); } - // Command 'checksum core' was moved to 'core verify-checksums'. - if ( isset( $config['checksum core'] ) ) { - $config['core verify-checksums'] = $config['checksum core']; - unset( $config['checksum core'] ); - } - // Command 'checksum plugin' was moved to 'plugin verify-checksums'. - if ( isset( $config['checksum plugin'] ) ) { - $config['plugin verify-checksums'] = $config['checksum plugin']; - unset( $config['checksum plugin'] ); + // 'core verify-checksums' -> 'checksum core' + if ( isset( $config['core verify-checksums'] ) ) { + $config['checksum core'] = $config['core verify-checksums']; + unset( $config['core verify-checksums'] ); } return $config; @@ -521,8 +336,6 @@ private static function load_yml( $yml_file ) { * Conform a variable to an array. * * @param mixed $val A string or an array - * - * @param-out array $val */ private static function arrayify( &$val ) { $val = (array) $val; @@ -535,37 +348,9 @@ private static function arrayify( &$val ) { * @param string $base Base path to prepend. */ private static function absolutize( &$path, $base ) { - if ( ! empty( $path ) ) { - $path = Path::expand_tilde( $path ); - if ( ! Path::is_absolute( $path ) ) { - $path = $base . DIRECTORY_SEPARATOR . $path; - } + if ( ! empty( $path ) && ! \WP_CLI\Utils\is_path_absolute( $path ) ) { + $path = $base . DIRECTORY_SEPARATOR . $path; } } - /** - * Interpolate environment variables in a string. - * - * Replaces ${env.VARIABLE_NAME} with the value of the VARIABLE_NAME environment variable. - * - * @param string $value The string value to interpolate. - * @return string The interpolated string. - */ - private static function interpolate_env_vars( $value ) { - if ( ! is_string( $value ) ) { - return $value; - } - - $result = preg_replace_callback( - '/\$\{env\.([A-Za-z0-9_]+)\}/', - function ( $matches ) { - $env_var = getenv( $matches[1] ); - return false !== $env_var ? $env_var : $matches[0]; - }, - $value - ); - - // Ensure we always return a string, even if preg_replace_callback fails. - return is_string( $result ) ? $result : $value; - } } diff --git a/php/WP_CLI/Context.php b/php/WP_CLI/Context.php deleted file mode 100644 index cd5b2e6e1..000000000 --- a/php/WP_CLI/Context.php +++ /dev/null @@ -1,30 +0,0 @@ -get_fake_admin_page(); - - // Bootstrap the WordPress administration area. - WP_CLI::add_wp_hook( - 'plugins_loaded', - function () use ( $config ) { - if ( isset( $config['user'] ) ) { - $fetcher = new User(); - $user = $fetcher->get_check( $config['user'] ); - $admin_user_id = $user->ID; - } else { - $admin_user_id = $this->find_admin_user_id(); - } - - /** - * @var int<1, max> $admin_user_id - */ - - WP_CLI::debug( sprintf( 'Continuing as admin user %d', $admin_user_id ), Context::DEBUG_GROUP ); - - $this->log_in_as_admin_user( $admin_user_id ); - }, - defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : -2147483648, // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound - 0 - ); - - WP_CLI::add_wp_hook( - 'wp_loaded', - function () { - $this->load_admin_environment(); - }, - defined( 'PHP_INT_MAX' ) ? PHP_INT_MAX : 2147483648, // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_maxFound - 0 - ); - } - - /** - * Find a suitable admin user ID for the current environment. - * - * On multisite, resolves a super admin via get_super_admins(). - * On single site, finds a user with the administrator role. - * - * @return int<1, max> Admin user ID. - */ - private function find_admin_user_id() { - if ( is_multisite() ) { - $super_admins = get_super_admins(); - - foreach ( $super_admins as $super_admin_login ) { - $user = get_user_by( 'login', $super_admin_login ); - if ( $user instanceof WP_User ) { - /** @var int<1, max> */ - return $user->ID; - } - } - - WP_CLI::error( 'No super admin user found. Specify one with --user=.' ); - } - - $admins = get_users( - [ - 'role' => 'administrator', - 'number' => 1, - 'orderby' => 'ID', - 'order' => 'ASC', - ] - ); - - if ( ! empty( $admins ) ) { - return $admins[0]->ID; - } - - WP_CLI::error( 'No administrator user found. Specify one with --user=.' ); - } - - /** - * Get a fake admin page filename that reflects the current command. - * - * Returns 'plugins.php' for `wp plugin` commands, 'themes.php' for - * `wp theme` commands, and 'wp-cli-fake-admin-file.php' otherwise. - * - * @return string Admin page filename. - */ - private function get_fake_admin_page(): string { - $command = WP_CLI::get_runner()->arguments; - - $command_map = [ - 'plugin' => 'plugins.php', - 'theme' => 'themes.php', - ]; - - $command_name = $command[0] ?? ''; - - return $command_map[ $command_name ] ?? 'wp-cli-fake-admin-file.php'; - } - - /** - * Ensure the current request is done under a logged-in administrator - * account. - * - * A lot of premium plugins/themes have their custom update routines locked - * behind an is_admin() call. - * - * @param int<1, max> $admin_user_id to log in as - * - * @return void - */ - private function log_in_as_admin_user( $admin_user_id ): void { - wp_set_current_user( $admin_user_id ); - - $expiration = time() + DAY_IN_SECONDS; - - $_COOKIE[ AUTH_COOKIE ] = wp_generate_auth_cookie( - $admin_user_id, - $expiration, - 'auth' - ); - - $_COOKIE[ SECURE_AUTH_COOKIE ] = wp_generate_auth_cookie( - $admin_user_id, - $expiration, - 'secure_auth' - ); - } - - /** - * Load the admin environment. - * - * This tries to load `wp-admin/admin.php` while trying to avoid issues - * like re-loading the wp-config.php file (which redeclares constants). - * - * To make this work across WordPress versions, we use the actual file and - * modify it on-the-fly. - * - * @global string $hook_suffix - * @global string $pagenow - * @global int $wp_db_version - * @global array $_wp_submenu_nopriv - * @global array $menu_order - * @global array $default_menu_order - * @global array $menu - * @global array $submenu - * @global array $compat - */ - private function load_admin_environment(): void { - global $compat, $default_menu_order, $hook_suffix, $menu, $menu_order, $pagenow, $submenu, $wp_db_version, $_wp_submenu_nopriv; - - if ( ! isset( $hook_suffix ) ) { - $hook_suffix = 'index'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - } - - // Make sure we don't trigger a DB upgrade as that tries to redirect - // the page. - - /** - * @var string $wp_db_version - */ - $wp_db_version = get_option( 'db_version' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $wp_db_version = (int) $wp_db_version; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - - // Ensure WP does not iterate over an undefined variable in - // `user_can_access_admin_page()`. - if ( ! isset( $_wp_submenu_nopriv ) ) { - $_wp_submenu_nopriv = []; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - } - - $admin_php_file = (string) file_get_contents( ABSPATH . 'wp-admin/admin.php' ); - - // First we remove the opening and closing PHP tags. - $admin_php_file = (string) preg_replace( '/^<\?php\s+/', '', $admin_php_file ); - $admin_php_file = (string) preg_replace( '/\s+\?>$/', '', $admin_php_file ); - - // Then we remove the loading of either wp-config.php or wp-load.php. - $admin_php_file = (string) preg_replace( '/^\s*(?:include|require).*[\'"]\/?wp-(?:load|config)\.php[\'"]\s*\)?;\s*$/m', '', $admin_php_file ); - - // We also remove the authentication redirect. - $admin_php_file = (string) preg_replace( '/^\s*auth_redirect\(\);$/m', '', $admin_php_file ); - - // Finally, we avoid sending headers. - $admin_php_file = (string) preg_replace( '/^\s*nocache_headers\(\);$/m', '', $admin_php_file ); - $_GET['noheader'] = true; - - eval( $admin_php_file ); // phpcs:ignore Squiz.PHP.Eval.Discouraged - } -} diff --git a/php/WP_CLI/Context/Auto.php b/php/WP_CLI/Context/Auto.php deleted file mode 100644 index f2eacceec..000000000 --- a/php/WP_CLI/Context/Auto.php +++ /dev/null @@ -1,92 +0,0 @@ - - */ - const COMMANDS_TO_RUN_AS_ADMIN = [ - [ 'plugin' ], - [ 'theme' ], - ]; - - /** - * Context manager instance to use. - * - * @var ContextManager - */ - private $context_manager; - - /** - * Instantiate an Auto object. - * - * @param ContextManager $context_manager Context manager instance to use. - */ - public function __construct( ContextManager $context_manager ) { - $this->context_manager = $context_manager; - } - - /** - * Process the context to set up the environment correctly. - * - * @param array $config Associative array of configuration data. - * @return void - * @throws WP_CLI\ExitException If an invalid context was deduced. - */ - public function process( $config ) { - $config['context'] = $this->deduce_best_context(); - - $this->context_manager->switch_context( $config ); - } - - /** - * Deduce the best context to run the current command in. - * - * @return string Context to use. - */ - private function deduce_best_context() { - if ( $this->is_command_to_run_as_admin() ) { - return Context::ADMIN; - } - - return Context::CLI; - } - - /** - * Check whether the current WP-CLI command is amongst those we want to - * run as admin. - * - * @return bool Whether the current command should be run as admin. - */ - private function is_command_to_run_as_admin(): bool { - $command = WP_CLI::get_runner()->arguments; - - foreach ( self::COMMANDS_TO_RUN_AS_ADMIN as $command_to_run_as_admin ) { - if ( - array_slice( $command, 0, count( $command_to_run_as_admin ) ) - === - $command_to_run_as_admin - ) { - WP_CLI::debug( - 'Detected a command to be intercepted: ' - . implode( ' ', $command ), - Context::DEBUG_GROUP - ); - return true; - } - } - - return false; - } -} diff --git a/php/WP_CLI/Context/Cli.php b/php/WP_CLI/Context/Cli.php deleted file mode 100644 index 686ca8652..000000000 --- a/php/WP_CLI/Context/Cli.php +++ /dev/null @@ -1,22 +0,0 @@ - - */ - private $contexts = []; - - /** - * Store the current context. - * - * @var string Current context. - */ - private $current_context = Context::CLI; - - /** - * Register a context with WP-CLI. - * - * @param string $name Name of the context. - * @param Context $implementation Implementation of the context. - */ - public function register_context( $name, Context $implementation ) { - $this->contexts[ $name ] = $implementation; - } - - /** - * Switch the context in which to run WP-CLI. - * - * @param array $config Associative array of configuration data. - * @return void - * - * @throws ExitException When an invalid context was requested. - */ - public function switch_context( $config ) { - $context = isset( $config['context'] ) - ? $config['context'] - : $this->current_context; - - if ( ! array_key_exists( $context, $this->contexts ) ) { - WP_CLI::error( "Unknown context '{$context}'" ); - } - - WP_CLI::debug( "Using context '{$context}'", Context::DEBUG_GROUP ); - - $this->current_context = $context; - $this->contexts[ $context ]->process( $config ); - } - - /** - * Return the current context. - * - * @return string Current context. - */ - public function get_context() { - return $this->current_context; - } -} diff --git a/php/WP_CLI/Dispatcher/CommandAddition.php b/php/WP_CLI/Dispatcher/CommandAddition.php index 9847abec4..0a51c4d29 100644 --- a/php/WP_CLI/Dispatcher/CommandAddition.php +++ b/php/WP_CLI/Dispatcher/CommandAddition.php @@ -34,7 +34,7 @@ final class CommandAddition { * @param string $reason Reason as to why the addition was aborted. */ public function abort( $reason = '' ) { - $this->abort = true; + $this->abort = true; $this->reason = (string) $reason; } diff --git a/php/WP_CLI/Dispatcher/CommandFactory.php b/php/WP_CLI/Dispatcher/CommandFactory.php index 2bb145da5..50af3ee28 100644 --- a/php/WP_CLI/Dispatcher/CommandFactory.php +++ b/php/WP_CLI/Dispatcher/CommandFactory.php @@ -2,14 +2,6 @@ namespace WP_CLI\Dispatcher; -use Closure; -use ReflectionClass; -use ReflectionFunction; -use ReflectionMethod; -use WP_CLI; -use WP_CLI\DocParser; -use WP_CLI\Utils; - /** * Creates CompositeCommand or Subcommand instances. * @@ -18,43 +10,35 @@ class CommandFactory { // Cache of file contents, indexed by filename. Only used if opcache.save_comments is disabled. - private static $file_contents = []; + private static $file_contents = array(); /** * Create a new CompositeCommand (or Subcommand if class has __invoke()) * - * @param string $name Represents how the command should be invoked - * @param string|callable-string|callable|array|object $callable A subclass of WP_CLI_Command, a function, or a closure - * @param RootCommand|CompositeCommand $parent The new command's parent Composite (or Root) command + * @param string $name Represents how the command should be invoked + * @param string $callable A subclass of WP_CLI_Command, a function, or a closure + * @param mixed $parent The new command's parent Composite (or Root) command */ public static function create( $name, $callable, $parent ) { - if ( ( is_object( $callable ) && ( $callable instanceof Closure ) ) + if ( ( is_object( $callable ) && ( $callable instanceof \Closure ) ) || ( is_string( $callable ) && function_exists( $callable ) ) ) { - $reflection = new ReflectionFunction( $callable ); - $command = self::create_subcommand( $parent, $name, $callable, $reflection ); - } elseif ( is_array( $callable ) && ( is_callable( $callable ) || Utils\is_valid_class_and_method_pair( $callable ) ) ) { - /** @var array{0:object|class-string,1:string} $callable */ - $reflection = new ReflectionClass( $callable[0] ); - $command = self::create_subcommand( - $parent, - $name, - [ $callable[0], $callable[1] ], + $reflection = new \ReflectionFunction( $callable ); + $command = self::create_subcommand( $parent, $name, $callable, $reflection ); + } elseif ( is_array( $callable ) && is_callable( $callable ) ) { + $reflection = new \ReflectionClass( $callable[0] ); + $command = self::create_subcommand( + $parent, $name, array( $callable[0], $callable[1] ), $reflection->getMethod( $callable[1] ) ); } else { - /** - * @var class-string $callable - */ - $reflection = new ReflectionClass( $callable ); + $reflection = new \ReflectionClass( $callable ); if ( $reflection->isSubclassOf( '\WP_CLI\Dispatcher\CommandNamespace' ) ) { $command = self::create_namespace( $parent, $name, $callable ); } elseif ( $reflection->hasMethod( '__invoke' ) ) { - $class = is_object( $callable ) ? $callable : $reflection->name; + $class = is_object( $callable ) ? $callable : $reflection->name; $command = self::create_subcommand( - $parent, - $name, - [ $class, '__invoke' ], + $parent, $name, array( $class, '__invoke' ), $reflection->getMethod( '__invoke' ) ); } else { @@ -69,24 +53,22 @@ public static function create( $name, $callable, $parent ) { * Clear the file contents cache. */ public static function clear_file_contents_cache() { - self::$file_contents = []; + self::$file_contents = array(); } /** * Create a new Subcommand instance. * - * @param RootCommand|CompositeCommand $parent The new command's parent Composite command. - * @param string|false $name Represents how the command should be invoked. - * If false, will be determined from the documented subject, represented by `$reflection`. - * @param mixed $callable A callable function or closure, or class name and method - * @param ReflectionClass|ReflectionMethod|ReflectionFunction $reflection Reflection instance, for doc parsing - * - * @template T of object - * @phpstan-param ReflectionClass|ReflectionMethod|ReflectionFunction $reflection + * @param mixed $parent The new command's parent Composite command + * @param string $name Represents how the command should be invoked + * @param mixed $callable A callable function or closure, or class name and method + * @param object $reflection Reflection instance, for doc parsing + * @param string $class A subclass of WP_CLI_Command + * @param string $method Class method to be called upon invocation. */ private static function create_subcommand( $parent, $name, $callable, $reflection ) { $doc_comment = self::get_doc_comment( $reflection ); - $docparser = new DocParser( $doc_comment ?: '' ); + $docparser = new \WP_CLI\DocParser( $doc_comment ); if ( is_array( $callable ) ) { if ( ! $name ) { @@ -98,57 +80,35 @@ private static function create_subcommand( $parent, $name, $callable, $reflectio } } if ( ! $doc_comment ) { - WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); + \WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); } $when_invoked = function ( $args, $assoc_args ) use ( $callable ) { if ( is_array( $callable ) ) { - $callable[0] = is_object( $callable[0] ) ? $callable[0] : new $callable[0](); - - /** - * @var callable $command - */ - $command = [ $callable[0], $callable[1] ]; - - call_user_func( $command, $args, $assoc_args ); + $callable[0] = is_object( $callable[0] ) ? $callable[0] : new $callable[0]; + call_user_func( array( $callable[0], $callable[1] ), $args, $assoc_args ); } else { - /** - * @var callable $callable - */ call_user_func( $callable, $args, $assoc_args ); } }; - /** - * @var string $name - */ - - $subcommand = new Subcommand( $parent, $name, $docparser, $when_invoked ); - - // Check for global argument conflicts - $path = \WP_CLI\Dispatcher\get_path( $subcommand ); - $command_name = implode( ' ', array_slice( $path, 1 ) ); - \WP_CLI::check_global_arg_conflicts( $command_name, $subcommand ); - - return $subcommand; + return new Subcommand( $parent, $name, $docparser, $when_invoked ); } /** * Create a new Composite command instance. * - * @param RootCommand|CompositeCommand $parent The new command's parent Root or Composite command - * @param string $name Represents how the command should be invoked - * @param class-string $callable + * @param mixed $parent The new command's parent Root or Composite command + * @param string $name Represents how the command should be invoked + * @param mixed $callable */ private static function create_composite_command( $parent, $name, $callable ) { - $reflection = new ReflectionClass( $callable ); + $reflection = new \ReflectionClass( $callable ); $doc_comment = self::get_doc_comment( $reflection ); if ( ! $doc_comment ) { - WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); - - $doc_comment = ''; + \WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); } - $docparser = new DocParser( $doc_comment ); + $docparser = new \WP_CLI\DocParser( $doc_comment ); $container = new CompositeCommand( $parent, $name, $docparser ); @@ -157,8 +117,8 @@ private static function create_composite_command( $parent, $name, $callable ) { continue; } - $class = is_object( $callable ) ? $callable : $reflection->name; - $subcommand = self::create_subcommand( $container, false, [ $class, $method->name ], $method ); + $class = is_object( $callable ) ? $callable : $reflection->name; + $subcommand = self::create_subcommand( $container, false, array( $class, $method->name ), $method ); $subcommand_name = $subcommand->get_name(); @@ -171,20 +131,17 @@ private static function create_composite_command( $parent, $name, $callable ) { /** * Create a new command namespace instance. * - * @param RootCommand|CompositeCommand $parent The new namespace's parent Root or Composite command. - * @param string $name Represents how the command should be invoked - * @param class-string $callable + * @param mixed $parent The new namespace's parent Root or Composite command. + * @param string $name Represents how the command should be invoked + * @param mixed $callable */ private static function create_namespace( $parent, $name, $callable ) { - $reflection = new ReflectionClass( $callable ); + $reflection = new \ReflectionClass( $callable ); $doc_comment = self::get_doc_comment( $reflection ); if ( ! $doc_comment ) { - WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); - - $doc_comment = ''; + \WP_CLI::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' ); } - - $docparser = new DocParser( $doc_comment ); + $docparser = new \WP_CLI\DocParser( $doc_comment ); return new CommandNamespace( $parent, $name, $docparser ); } @@ -204,15 +161,11 @@ private static function is_good_method( $method ) { * * @param ReflectionMethod|ReflectionClass|ReflectionFunction $reflection Reflection instance. * @return string|false|null Doc comment string if any, false if none (same as `Reflection*::getDocComment()`), null if error. - * - * @template T of object - * @phpstan-param ReflectionClass|ReflectionMethod|ReflectionFunction $reflection */ private static function get_doc_comment( $reflection ) { - $contents = null; $doc_comment = $reflection->getDocComment(); - if ( false !== $doc_comment || ! ( ini_get( 'opcache.enable_cli' ) && ! ini_get( 'opcache.save_comments' ) ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.NewIniDirectives + if ( false !== $doc_comment || ! ( ini_get( 'opcache.enable_cli' ) && ! ini_get( 'opcache.save_comments' ) ) ) { // Either have doc comment, or no doc comment and save comments enabled - standard situation. if ( ! getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ) ) { return $doc_comment; @@ -221,69 +174,48 @@ private static function get_doc_comment( $reflection ) { $filename = $reflection->getFileName(); - if ( $filename ) { - if ( isset( self::$file_contents[ $filename ] ) ) { - $contents = self::$file_contents[ $filename ]; - } elseif ( is_readable( $filename ) ) { - $contents = file_get_contents( $filename ); - if ( is_string( $contents ) && '' !== $contents ) { - $contents = explode( "\n", $contents ); - self::$file_contents[ $filename ] = $contents; - } - } - } - - if ( ! empty( $contents ) ) { - return self::extract_last_doc_comment( implode( "\n", array_slice( $contents, 0, $reflection->getStartLine() ?: 0 ) ) ); + if ( isset( self::$file_contents[ $filename ] ) ) { + $contents = self::$file_contents[ $filename ]; + } elseif ( is_readable( $filename ) && ( $contents = file_get_contents( $filename ) ) ) { + self::$file_contents[ $filename ] = $contents = explode( "\n", $contents ); + } else { + \WP_CLI::debug( "Could not read contents for filename '{$filename}'.", 'commandfactory' ); + return null; } - WP_CLI::debug( "Could not read contents for filename '{$filename}'.", 'commandfactory' ); - return null; + return self::extract_last_doc_comment( implode( "\n", array_slice( $contents, 0, $reflection->getStartLine() ) ) ); } /** * Returns the last doc comment if any in `$content`. * * @param string $content The content, which should end at the class or function declaration. - * @return string|false The last doc comment if any, or false if none. + * @return string|bool The last doc comment if any, or false if none. */ private static function extract_last_doc_comment( $content ) { - $content = trim( $content ); + $content = trim( $content ); $comment_end_pos = strrpos( $content, '*/' ); - if ( false === $comment_end_pos ) { return false; } - // Make sure comment end belongs to this class/function. if ( preg_match_all( '/(?:^|[\s;}])(?:class|function)\s+/', substr( $content, $comment_end_pos + 2 ), $dummy /*needed for PHP 5.3*/ ) > 1 ) { return false; } - - $content = substr( $content, 0, $comment_end_pos + 2 ); - $comment_start_pos = strrpos( $content, '/**' ); - - if ( false === $comment_start_pos || ( $comment_start_pos + 2 ) === $comment_end_pos ) { + $content = substr( $content, 0, $comment_end_pos + 2 ); + if ( false === ( $comment_start_pos = strrpos( $content, '/**' ) ) || $comment_start_pos + 2 === $comment_end_pos ) { return false; } - // Make sure comment start belongs to this comment end. - $comment_end2_pos = strpos( substr( $content, $comment_start_pos ), '*/' ); - - if ( false !== $comment_end2_pos && ( $comment_start_pos + $comment_end2_pos ) < $comment_end_pos ) { + if ( false !== ( $comment_end2_pos = strpos( substr( $content, $comment_start_pos ), '*/' ) ) && $comment_start_pos + $comment_end2_pos < $comment_end_pos ) { return false; } - // Allow for '/**' within doc comment. - $subcontent = substr( $content, 0, $comment_start_pos ); - $comment_start2_pos = strrpos( $subcontent, '/**' ); - - while ( false !== $comment_start2_pos && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) { - $comment_start_pos = $comment_start2_pos; - $subcontent = substr( $subcontent, 0, $comment_start_pos ); - $comment_start2_pos = strrpos( $subcontent, '/**' ); + $subcontent = substr( $content, 0, $comment_start_pos ); + while ( false !== ( $comment_start2_pos = strrpos( $subcontent, '/**' ) ) && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) { + $comment_start_pos = $comment_start2_pos; + $subcontent = substr( $subcontent, 0, $comment_start_pos ); } - return substr( $content, $comment_start_pos, $comment_end_pos + 2 ); } } diff --git a/php/WP_CLI/Dispatcher/CommandNamespace.php b/php/WP_CLI/Dispatcher/CommandNamespace.php index 51ef3f2b8..569d1ca5a 100644 --- a/php/WP_CLI/Dispatcher/CommandNamespace.php +++ b/php/WP_CLI/Dispatcher/CommandNamespace.php @@ -24,27 +24,27 @@ class CommandNamespace extends CompositeCommand { public function show_usage() { $methods = $this->get_subcommands(); - $i = 0; + $i = 0; $count = 0; - foreach ( $methods as $subcommand ) { - $prefix = ( 0 === $i ) ? 'usage: ' : ' or: '; - ++$i; + foreach ( $methods as $name => $subcommand ) { + $prefix = ( 0 == $i++ ) ? 'usage: ' : ' or: '; if ( \WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) { continue; } \WP_CLI::line( $subcommand->get_usage( $prefix ) ); - ++$count; + $count++; } $cmd_name = implode( ' ', array_slice( get_path( $this ), 1 ) ); - $message = $count > 0 + $message = $count > 0 ? "See 'wp help $cmd_name ' for more information on a specific command." : "The namespace $cmd_name does not contain any usable commands in the current context."; \WP_CLI::line(); \WP_CLI::line( $message ); + } } diff --git a/php/WP_CLI/Dispatcher/CompositeCommand.php b/php/WP_CLI/Dispatcher/CompositeCommand.php index ed9e59fe4..a00358f78 100644 --- a/php/WP_CLI/Dispatcher/CompositeCommand.php +++ b/php/WP_CLI/Dispatcher/CompositeCommand.php @@ -2,9 +2,7 @@ namespace WP_CLI\Dispatcher; -use WP_CLI; -use WP_CLI\DocParser; -use WP_CLI\Utils; +use \WP_CLI\Utils; /** * A non-leaf node in the command tree. @@ -14,44 +12,36 @@ */ class CompositeCommand { - protected $name; - protected $shortdesc; - protected $longdesc; - protected $synopsis; - protected $hook; - protected $docparser; + protected $name, $shortdesc, $synopsis, $docparser; - protected $parent; - protected $subcommands = []; + protected $parent, $subcommands = array(); /** * Instantiate a new CompositeCommand * - * @param RootCommand|CompositeCommand $parent_command Parent command (either Root or Composite) - * @param string $name Represents how command should be invoked - * @param DocParser $docparser + * @param mixed $parent Parent command (either Root or Composite) + * @param string $name Represents how command should be invoked + * @param \WP_CLI\DocParser */ - public function __construct( $parent_command, $name, $docparser ) { - $this->parent = $parent_command; + public function __construct( $parent, $name, $docparser ) { + $this->parent = $parent; $this->name = $name; $this->shortdesc = $docparser->get_shortdesc(); - $this->longdesc = $docparser->get_longdesc(); + $this->longdesc = $docparser->get_longdesc(); $this->docparser = $docparser; - $this->hook = $parent_command->get_hook(); $when_to_invoke = $docparser->get_tag( 'when' ); if ( $when_to_invoke ) { - $this->hook = $when_to_invoke; - WP_CLI::get_runner()->register_early_invoke( $when_to_invoke, $this ); + \WP_CLI::get_runner()->register_early_invoke( $when_to_invoke, $this ); } } /** * Get the parent composite (or root) command * - * @return RootCommand|CompositeCommand + * @return mixed */ public function get_parent() { return $this->parent; @@ -61,15 +51,11 @@ public function get_parent() { * Add a named subcommand to this composite command's * set of contained subcommands. * - * @param string $name Represents how subcommand should be invoked - * @param Subcommand|CompositeCommand $command Cub-command to add. - * @param bool $override Optional. Whether to override an existing subcommand of the same - * name. + * @param string $name Represents how subcommand should be invoked + * @param \WP_CLI\Dispatcher\Subcommand */ - public function add_subcommand( $name, $command, $override = true ) { - if ( $override || ! array_key_exists( $name, $this->subcommands ) ) { - $this->subcommands[ $name ] = $command; - } + public function add_subcommand( $name, $command ) { + $this->subcommands[ $name ] = $command; } /** @@ -88,7 +74,7 @@ public function remove_subcommand( $name ) { /** * Composite commands always contain subcommands. * - * @return bool + * @return true */ public function can_have_subcommands() { return true; @@ -125,32 +111,13 @@ public function get_shortdesc() { return $this->shortdesc; } - /** - * Get the hook name for this composite - * command. - * - * @return string - */ - public function get_hook() { - return $this->hook; - } - - /** - * Get the DocParser instance for this command. - * - * @return DocParser|null - */ - public function get_docparser() { - return $this->docparser; - } - /** * Set the short description for this composite command. * - * @param string $shortdesc + * @param string */ public function set_shortdesc( $shortdesc ) { - $this->shortdesc = Utils\normalize_eols( $shortdesc ); + $this->shortdesc = $shortdesc; } /** @@ -166,10 +133,10 @@ public function get_longdesc() { /** * Set the long description for this composite command * - * @param string $longdesc + * @param string */ public function set_longdesc( $longdesc ) { - $this->longdesc = Utils\normalize_eols( $longdesc ); + $this->longdesc = $longdesc; } /** @@ -187,7 +154,6 @@ public function get_synopsis() { /** * Get the usage for this composite command. * - * @param string $prefix * @return string */ public function get_usage( $prefix ) { @@ -208,24 +174,20 @@ public function show_usage() { $i = 0; - foreach ( $methods as $subcommand ) { - $prefix = ( 0 === $i ) ? 'usage: ' : ' or: '; - ++$i; + foreach ( $methods as $name => $subcommand ) { + $prefix = ( 0 == $i++ ) ? 'usage: ' : ' or: '; - $disabled_reason = WP_CLI::get_runner()->get_command_disabled_reason( $subcommand ); - if ( false !== $disabled_reason ) { - $suffix = $disabled_reason ? " (disabled: $disabled_reason)" : ' (disabled)'; - WP_CLI::line( $subcommand->get_usage( $prefix ) . $suffix ); + if ( \WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) { continue; } - WP_CLI::line( $subcommand->get_usage( $prefix ) ); + \WP_CLI::line( $subcommand->get_usage( $prefix ) ); } $cmd_name = implode( ' ', array_slice( get_path( $this ), 1 ) ); - WP_CLI::line(); - WP_CLI::line( "See 'wp help $cmd_name ' for more information on a specific command." ); + \WP_CLI::line(); + \WP_CLI::line( "See 'wp help $cmd_name ' for more information on a specific command." ); } /** @@ -236,7 +198,7 @@ public function show_usage() { * @param array $assoc_args * @param array $extra_args */ - public function invoke( $args, $assoc_args, $extra_args ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- arguments not used, as only help displayed. + public function invoke( $args, $assoc_args, $extra_args ) { $this->show_usage(); } @@ -245,7 +207,7 @@ public function invoke( $args, $assoc_args, $extra_args ) { // phpcs:ignore Gene * subcommand * * @param array $args - * @return Subcommand|false + * @return \WP_CLI\Dispatcher\Subcommand|false */ public function find_subcommand( &$args ) { $name = array_shift( $args ); @@ -275,7 +237,7 @@ public function find_subcommand( &$args ) { * @return array */ private static function get_aliases( $subcommands ) { - $aliases = []; + $aliases = array(); foreach ( $subcommands as $name => $subcommand ) { $alias = $subcommand->get_alias(); @@ -290,7 +252,7 @@ private static function get_aliases( $subcommands ) { /** * Composite commands can only be known by one name. * - * @return string|false + * @return false */ public function get_alias() { return false; @@ -303,14 +265,14 @@ public function get_alias() { * @return string */ protected function get_global_params( $root_command = false ) { - $binding = []; + $binding = array(); $binding['root_command'] = $root_command; - if ( ! $this->can_have_subcommands() || ( is_object( $this->parent ) && get_class( $this->parent ) === 'WP_CLI\Dispatcher\CompositeCommand' ) ) { + if ( ! $this->can_have_subcommands() || ( is_object( $this->parent ) && get_class( $this->parent ) == 'WP_CLI\Dispatcher\CompositeCommand' ) ) { $binding['is_subcommand'] = true; } - foreach ( WP_CLI::get_configurator()->get_spec() as $key => $details ) { + foreach ( \WP_CLI::get_configurator()->get_spec() as $key => $details ) { if ( false === $details['runtime'] ) { continue; } @@ -329,14 +291,10 @@ protected function get_global_params( $root_command = false ) { $synopsis = "--$key" . $details['runtime']; } - // Check if global parameters synopsis should be displayed or not. - if ( 'true' !== Utils\get_env_or_config( 'WP_CLI_SUPPRESS_GLOBAL_PARAMS' ) ) { - $binding['parameters'][] = [ - 'synopsis' => $synopsis, - 'desc' => $details['desc'], - ]; - $binding['has_parameters'] = true; - } + $binding['parameters'][] = array( + 'synopsis' => $synopsis, + 'desc' => $details['desc'], + ); } if ( $this->get_subcommands() ) { @@ -346,3 +304,4 @@ protected function get_global_params( $root_command = false ) { return Utils\mustache_render( 'man-params.mustache', $binding ); } } + diff --git a/php/WP_CLI/Dispatcher/DisabledCommand.php b/php/WP_CLI/Dispatcher/DisabledCommand.php deleted file mode 100644 index 0d086b0ac..000000000 --- a/php/WP_CLI/Dispatcher/DisabledCommand.php +++ /dev/null @@ -1,57 +0,0 @@ -disabled_reason = $reason; - } - - /** - * Get the reason why the command is disabled. - * - * @return string - */ - public function get_disabled_reason() { - return $this->disabled_reason; - } - - /** - * Prevent execution of the command. - * - * @param array $args - * @param array $assoc_args - * @param array $extra_args - */ - public function invoke( $args, $assoc_args, $extra_args ) { - $cmd_path = implode( ' ', get_path( $this ) ); - $reason = $this->disabled_reason ? " Reason: {$this->disabled_reason}" : ''; - WP_CLI::error( sprintf( "The '%s' command has been disabled.%s", $cmd_path, $reason ) ); - } -} diff --git a/php/WP_CLI/Dispatcher/RootCommand.php b/php/WP_CLI/Dispatcher/RootCommand.php index e2d36b1fb..0ecb3c816 100644 --- a/php/WP_CLI/Dispatcher/RootCommand.php +++ b/php/WP_CLI/Dispatcher/RootCommand.php @@ -2,7 +2,7 @@ namespace WP_CLI\Dispatcher; -use WP_CLI\Utils; +use \WP_CLI\Utils; /** * The root node in the command tree. @@ -11,9 +11,6 @@ */ class RootCommand extends CompositeCommand { - /** - * Instantiate a new RootCommand. - */ public function __construct() { $this->parent = false; @@ -36,7 +33,7 @@ public function get_longdesc() { * command. * * @param array $args - * @return Subcommand|false + * @return \WP_CLI\Dispatcher\Subcommand|false */ public function find_subcommand( &$args ) { $command = array_shift( $args ); @@ -49,4 +46,14 @@ public function find_subcommand( &$args ) { return $this->subcommands[ $command ]; } + + /** + * Get all registered subcommands. + * + * @return array + */ + public function get_subcommands() { + return parent::get_subcommands(); + } } + diff --git a/php/WP_CLI/Dispatcher/Subcommand.php b/php/WP_CLI/Dispatcher/Subcommand.php index 24ef8f42f..3aa01f286 100644 --- a/php/WP_CLI/Dispatcher/Subcommand.php +++ b/php/WP_CLI/Dispatcher/Subcommand.php @@ -3,9 +3,6 @@ namespace WP_CLI\Dispatcher; use WP_CLI; -use WP_CLI\DocParser; -use WP_CLI\SynopsisParser; -use WP_CLI\SynopsisValidator; use WP_CLI\Utils; /** @@ -15,35 +12,17 @@ */ class Subcommand extends CompositeCommand { - /** - * Alias for the subcommand. - * - * @var string - */ private $alias; - /** - * Callable to execute when the subcommand is invoked. - * - * @var callable - */ private $when_invoked; - /** - * Initiate a new Subcommand. - * - * @param RootCommand|CompositeCommand $parent Parent command. - * @param string $name Command name. - * @param DocParser $docparser DocParser instance. - * @param callable $when_invoked Invocation callback. - */ public function __construct( $parent, $name, $docparser, $when_invoked ) { - $this->alias = $docparser->get_tag( 'alias' ); - parent::__construct( $parent, $name, $docparser ); $this->when_invoked = $when_invoked; + $this->alias = $docparser->get_tag( 'alias' ); + $this->synopsis = $docparser->get_synopsis(); if ( ! $this->synopsis && $this->longdesc ) { $this->synopsis = self::extract_synopsis( $this->longdesc ); @@ -85,7 +64,7 @@ public function get_synopsis() { /** * Set the synopsis string for this subcommand. * - * @param string $synopsis + * @param string */ public function set_synopsis( $synopsis ) { $this->synopsis = $synopsis; @@ -130,10 +109,10 @@ public function get_usage( $prefix ) { * Wrapper for CLI Tools' prompt() method. * * @param string $question - * @param mixed $default + * @param string $default * @return string|false */ - private function prompt( $question, $default = null ) { + private function prompt( $question, $default ) { $question .= ': '; if ( function_exists( 'readline' ) ) { @@ -142,49 +121,13 @@ private function prompt( $question, $default = null ) { echo $question; - $ret = (string) stream_get_line( STDIN, 1024, "\n" ); + $ret = stream_get_line( STDIN, 1024, "\n" ); if ( Utils\is_windows() && "\r" === substr( $ret, -1 ) ) { $ret = substr( $ret, 0, -1 ); } return $ret; } - /** - * Get the description for an argument from documentation. - * - * @param array $spec_arg Argument specification from SynopsisParser - * @param DocParser $docparser DocParser instance for retrieving descriptions - * @param string $longdesc Long description text for regex matching - * @return string Description text, or empty string if not found - */ - private function get_arg_description( $spec_arg, $docparser, $longdesc ) { - $description = ''; - - if ( 'positional' === $spec_arg['type'] ) { - $description = $docparser->get_arg_desc( $spec_arg['name'] ); - // If get_arg_desc doesn't find it (e.g., for simple without modifiers), - // try a simpler pattern that matches followed by : description, - // using a pattern consistent with DocParser::get_arg_desc(). - if ( empty( $description ) ) { - $arg_pattern = '/\[?<' . preg_quote( $spec_arg['name'], '/' ) . ">.+\n:\s*(.+?)(\n|$)/"; - if ( preg_match( $arg_pattern, $longdesc, $matches ) ) { - $description = trim( $matches[1] ); - } - } - } elseif ( 'assoc' === $spec_arg['type'] ) { - $description = $docparser->get_param_desc( $spec_arg['name'] ); - } elseif ( 'flag' === $spec_arg['type'] ) { - // For flags, the pattern is [--flag] not [--flag=] - // So we need a custom regex pattern in the longdesc - $flag_pattern = '/\[?--' . preg_quote( $spec_arg['name'], '/' ) . "\]\s*\n:\s*(.+?)(\n|$)/"; - if ( preg_match( $flag_pattern, $longdesc, $matches ) ) { - $description = trim( $matches[1] ); - } - } - - return $description; - } - /** * Interactively prompt the user for input * based on defined synopsis and passed arguments. @@ -198,54 +141,26 @@ private function prompt_args( $args, $assoc_args ) { $synopsis = $this->get_synopsis(); if ( ! $synopsis ) { - return [ $args, $assoc_args ]; + return array( $args, $assoc_args ); } - // Create a docparser to get default values and descriptions - $docparser = $this->create_mock_docparser(); - - // To skip the already provided positional arguments, we need to count - // how many we had already received. - $arg_index = 0; - $spec = array_filter( - SynopsisParser::parse( $synopsis ), - function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) { - switch ( $spec_arg['type'] ) { - case 'positional': - // Only prompt for the positional arguments that are not - // yet provided, based purely on number. - return $arg_index++ >= count( $args ); - case 'generic': - // Always prompt for generic arguments. - return true; - case 'assoc': - case 'flag': - default: - // Prompt for the specific flags that were not provided - // yet, based on name. - return ! isset( $assoc_args[ $spec_arg['name'] ] ); - } + \WP_CLI\SynopsisParser::parse( $synopsis ), + function( $spec_arg ) { + return in_array( $spec_arg['type'], array( 'generic', 'positional', 'assoc', 'flag' ) ); } ); $spec = array_values( $spec ); - /** - * @var string|true $prompt_args - */ $prompt_args = WP_CLI::get_config( 'prompt' ); if ( true !== $prompt_args ) { $prompt_args = explode( ',', $prompt_args ); } - // Reuse the existing DocParser to retrieve argument descriptions. - $docparser = $this->docparser; - // 'positional' arguments are positional (aka zero-indexed) // so $args needs to be reset before prompting for new arguments - $args = []; - + $args = array(); foreach ( $spec as $key => $spec_arg ) { // When prompting for specific arguments (e.g. --prompt=user_pass), @@ -254,24 +169,16 @@ function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) { if ( 'assoc' !== $spec_arg['type'] ) { continue; } - $matched = in_array( $spec_arg['name'], $prompt_args, true ); - if ( ! $matched && ! empty( $spec_arg['aliases'] ) ) { - foreach ( $spec_arg['aliases'] as $alias ) { - if ( in_array( $alias, $prompt_args, true ) ) { - $matched = true; - break; - } - } - } - if ( ! $matched ) { + if ( ! in_array( $spec_arg['name'], $prompt_args, true ) ) { continue; } } $current_prompt = ( $key + 1 ) . '/' . count( $spec ) . ' '; + $default = $spec_arg['optional'] ? '' : false; // 'generic' permits arbitrary key=value (e.g. [--=] ) - if ( 'generic' === $spec_arg['type'] ) { + if ( 'generic' == $spec_arg['type'] ) { list( $key_token, $value_token ) = explode( '=', $spec_arg['token'] ); @@ -283,18 +190,18 @@ function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) { $key_prompt = str_repeat( ' ', strlen( $current_prompt ) ) . $key_token; } - $key = $this->prompt( $key_prompt ); + $key = $this->prompt( $key_prompt, $default ); if ( false === $key ) { - return [ $args, $assoc_args ]; + return array( $args, $assoc_args ); } if ( $key ) { $key_prompt_count = strlen( $key_prompt ) - strlen( $value_token ) - 1; - $value_prompt = str_repeat( ' ', $key_prompt_count ) . '=' . $value_token; + $value_prompt = str_repeat( ' ', $key_prompt_count ) . '=' . $value_token; - $value = $this->prompt( $value_prompt ); + $value = $this->prompt( $value_prompt, $default ); if ( false === $value ) { - return [ $args, $assoc_args ]; + return array( $args, $assoc_args ); } $assoc_args[ $key ] = $value; @@ -306,47 +213,24 @@ function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) { } while ( $repeat ); } else { - $prompt = $current_prompt . $spec_arg['token']; - $default_val = null; - // Add description if available - $longdesc = $this->get_longdesc(); - $description = $this->get_arg_description( $spec_arg, $docparser, $longdesc ); - - if ( ! empty( $description ) ) { - $prompt .= ' (' . $description . ')'; - } - - // Get default value for the argument (not for flags) - if ( 'flag' === $spec_arg['type'] ) { + $prompt = $current_prompt . $spec_arg['token']; + if ( 'flag' == $spec_arg['type'] ) { $prompt .= ' (Y/n)'; - } elseif ( 'positional' === $spec_arg['type'] || 'assoc' === $spec_arg['type'] ) { - $spec_args = ( 'positional' === $spec_arg['type'] ) - ? $docparser->get_arg_args( $spec_arg['name'] ) - : $docparser->get_param_args( $spec_arg['name'] ); - if ( null !== $spec_args && isset( $spec_args['default'] ) ) { - $default_val = $spec_args['default']; - $prompt .= ' [' . $default_val . ']'; - } } - $response = $this->prompt( $prompt ); + $response = $this->prompt( $prompt, $default ); if ( false === $response ) { - return [ $args, $assoc_args ]; + return array( $args, $assoc_args ); } - // If response is empty and there's a default (not a flag), use the default - if ( '' === $response && null !== $default_val ) { - $response = $default_val; - } - - if ( '' !== $response ) { + if ( $response ) { switch ( $spec_arg['type'] ) { case 'positional': if ( $spec_arg['repeating'] ) { $response = explode( ' ', $response ); } else { - $response = [ $response ]; + $response = array( $response ); } $args = array_merge( $args, $response ); break; @@ -354,7 +238,7 @@ function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) { $assoc_args[ $spec_arg['name'] ] = $response; break; case 'flag': - if ( 'Y' === strtoupper( $response ) ) { + if ( 'Y' == strtoupper( $response ) ) { $assoc_args[ $spec_arg['name'] ] = true; } break; @@ -363,86 +247,7 @@ function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) { } } - return [ $args, $assoc_args ]; - } - - /** - * Create a DocParser instance from the command's description. - * - * This creates a mock DocParser from the command's short and long descriptions, - * used internally for getting argument metadata. - * - * @return DocParser - */ - private function create_mock_docparser() { - $mock_doc = [ $this->get_shortdesc(), '' ]; - $mock_doc = array_merge( $mock_doc, explode( "\n", $this->get_longdesc() ) ); - $mock_doc = '/**' . PHP_EOL . '* ' . implode( PHP_EOL . '* ', $mock_doc ) . PHP_EOL . '*/'; - return new DocParser( $mock_doc ); - } - - /** - * Resolve argument aliases to their canonical names. - * - * Takes an associative array of arguments and replaces any aliases - * with their canonical parameter names. This allows commands to define - * shorter versions of arguments (e.g., -w for --with-dependencies). - * - * For repeating parameters, alias values are merged with any canonical - * values already provided rather than being discarded. - * - * @param array $assoc_args Arguments passed to command. - * @param array $aliases Map of alias => canonical_name. - * @param array $repeating_params Map of canonical_name => true for repeating params. - * @return array Arguments with aliases resolved to canonical names. - */ - private function resolve_arg_aliases( $assoc_args, $aliases, $repeating_params = [] ) { - if ( empty( $aliases ) ) { - return $assoc_args; - } - - // First pass: copy all non-alias entries to $resolved_args. - $resolved_args = []; - foreach ( $assoc_args as $key => $value ) { - if ( ! isset( $aliases[ $key ] ) ) { - $resolved_args[ $key ] = $value; - } - } - - // Second pass: resolve aliases. - foreach ( $assoc_args as $key => $value ) { - if ( ! isset( $aliases[ $key ] ) ) { - continue; - } - - $canonical_key = $aliases[ $key ]; - WP_CLI::debug( "Alias resolved: --{$key} => --{$canonical_key}", 'bootstrap' ); - - if ( ! array_key_exists( $canonical_key, $resolved_args ) ) { - // Canonical name not yet present; use alias value. - $resolved_args[ $canonical_key ] = $value; - } elseif ( ! empty( $repeating_params[ $canonical_key ] ) ) { - // Canonical name present and parameter is repeating; merge values. - $existing = $resolved_args[ $canonical_key ]; - if ( ! is_array( $existing ) ) { - $existing = [ $existing ]; - } - $alias_values = is_array( $value ) ? $value : [ $value ]; - $resolved_args[ $canonical_key ] = array_merge( $existing, $alias_values ); - } else { - // Canonical name present and not repeating; canonical wins. - WP_CLI::debug( - sprintf( - 'Ignoring alias --%s because --%s was already provided.', - $key, - $canonical_key - ), - 'bootstrap' - ); - } - } - - return $resolved_args; + return array( $args, $assoc_args ); } /** @@ -458,10 +263,10 @@ private function resolve_arg_aliases( $assoc_args, $aliases, $repeating_params = private function validate_args( $args, $assoc_args, $extra_args ) { $synopsis = $this->get_synopsis(); if ( ! $synopsis ) { - return [ [], $args, $assoc_args, $extra_args ]; + return array( array(), $args, $assoc_args, $extra_args ); } - $validator = new SynopsisValidator( $synopsis ); + $validator = new \WP_CLI\SynopsisValidator( $synopsis ); $cmd_path = implode( ' ', get_path( $this ) ); foreach ( $validator->get_unknown() as $token ) { @@ -487,13 +292,16 @@ private function validate_args( $args, $assoc_args, $extra_args ) { ); } - $synopsis_spec = SynopsisParser::parse( $synopsis ); - $i = 0; - $errors = [ - 'fatal' => [], - 'warning' => [], - ]; - $docparser = $this->create_mock_docparser(); + $synopsis_spec = \WP_CLI\SynopsisParser::parse( $synopsis ); + $i = 0; + $errors = array( + 'fatal' => array(), + 'warning' => array(), + ); + $mock_doc = array( $this->get_shortdesc(), '' ); + $mock_doc = array_merge( $mock_doc, explode( "\n", $this->get_longdesc() ) ); + $mock_doc = '/**' . PHP_EOL . '* ' . implode( PHP_EOL . '* ', $mock_doc ) . PHP_EOL . '*/'; + $docparser = new \WP_CLI\DocParser( $mock_doc ); foreach ( $synopsis_spec as $spec ) { if ( 'positional' === $spec['type'] ) { $spec_args = $docparser->get_arg_args( $spec['name'] ); @@ -505,88 +313,43 @@ private function validate_args( $args, $assoc_args, $extra_args ) { if ( isset( $spec_args['options'] ) ) { if ( ! empty( $spec['repeating'] ) ) { do { - // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design. if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { \WP_CLI::error( 'Invalid value specified for positional arg.' ); } - ++$i; + $i++; } while ( isset( $args[ $i ] ) ); - } elseif ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design. - \WP_CLI::error( 'Invalid value specified for positional arg.' ); + } else { + if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { + \WP_CLI::error( 'Invalid value specified for positional arg.' ); + } } } - ++$i; + $i++; } elseif ( 'assoc' === $spec['type'] ) { $spec_args = $docparser->get_param_args( $spec['name'] ); - - // Handle repeating parameter (e.g., [--status=...]) - if ( isset( $assoc_args[ $spec['name'] ] ) && is_array( $assoc_args[ $spec['name'] ] ) ) { - // If repeating is not set, use only the last value - if ( empty( $spec['repeating'] ) ) { - $values = $assoc_args[ $spec['name'] ]; - $values_count = count( $values ); - if ( $values_count > 0 ) { - $assoc_args[ $spec['name'] ] = $values[ $values_count - 1 ]; - } - } - } - if ( ! isset( $assoc_args[ $spec['name'] ] ) && ! isset( $extra_args[ $spec['name'] ] ) ) { if ( isset( $spec_args['default'] ) ) { $assoc_args[ $spec['name'] ] = $spec_args['default']; } } if ( isset( $assoc_args[ $spec['name'] ] ) && isset( $spec_args['options'] ) ) { - /** - * @var string|string[] $value - */ - $value = $assoc_args[ $spec['name'] ]; - $options = $spec_args['options']; - - // Handle validation for multiple values - if ( is_array( $value ) ) { - foreach ( $value as $single_value ) { - // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design. - if ( ! in_array( $single_value, $options ) ) { - $errors['fatal'][ $spec['name'] ] = "Invalid value '{$single_value}' specified for '{$spec['name']}'"; - break; - } - } - } elseif ( ! in_array( $value, $options ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design. - // Try whether it might be a comma-separated list of multiple values. - $values = array_map( 'trim', explode( ',', $value ) ); - $count = count( $values ); - if ( - $count > 1 - && - count( - array_filter( - $values, - static function ( $value ) use ( $options ) { - return in_array( $value, $options, true ); - } - ) - ) === $count - ) { - continue; - } + if ( ! in_array( $assoc_args[ $spec['name'] ], $spec_args['options'] ) ) { $errors['fatal'][ $spec['name'] ] = "Invalid value specified for '{$spec['name']}'"; } } } } - $config = \WP_CLI::get_config(); list( $returned_errors, $to_unset ) = $validator->validate_assoc( - array_merge( $config, $extra_args, $assoc_args ) + array_merge( \WP_CLI::get_config(), $extra_args, $assoc_args ) ); - foreach ( [ 'fatal', 'warning' ] as $error_type ) { + foreach ( array( 'fatal', 'warning' ) as $error_type ) { $errors[ $error_type ] = array_merge( $errors[ $error_type ], $returned_errors[ $error_type ] ); } if ( 'help' !== $this->name ) { foreach ( $validator->unknown_assoc( $assoc_args ) as $key ) { - $suggestion = Utils\get_suggestion( + $suggestion = Utils\get_suggestion( $key, $this->get_parameters( $synopsis_spec ), $threshold = 2 @@ -604,8 +367,7 @@ static function ( $value ) use ( $options ) { $out = 'Parameter errors:'; foreach ( $errors['fatal'] as $key => $error ) { $out .= "\n {$error}"; - $desc = $docparser->get_param_desc( $key ); - if ( '' !== $desc ) { + if ( $desc = $docparser->get_param_desc( $key ) ) { $out .= " ({$desc})"; } } @@ -615,35 +377,7 @@ static function ( $value ) use ( $options ) { array_map( '\\WP_CLI::warning', $errors['warning'] ); - return [ $to_unset, $args, $assoc_args, $extra_args ]; - } - - /** - * Get the list of sensitive argument names from the synopsis. - * These arguments will have their values masked in log output. - * - * @return array Array of argument names that are marked as sensitive - */ - private function get_sensitive_args() { - $synopsis = $this->get_synopsis(); - if ( ! $synopsis ) { - return []; - } - - $synopsis_spec = SynopsisParser::parse( $synopsis ); - $docparser = $this->create_mock_docparser(); - $sensitive_args = []; - - foreach ( $synopsis_spec as $spec ) { - if ( 'assoc' === $spec['type'] ) { - $spec_args = $docparser->get_param_args( $spec['name'] ); - if ( isset( $spec_args['sensitive'] ) && $spec_args['sensitive'] ) { - $sensitive_args[] = $spec['name']; - } - } - } - - return $sensitive_args; + return array( $to_unset, $args, $assoc_args, $extra_args ); } /** @@ -656,80 +390,13 @@ private function get_sensitive_args() { */ public function invoke( $args, $assoc_args, $extra_args ) { static $prompted_once = false; - - // Build alias map from the parsed synopsis and resolve to canonical names. - $aliases = []; - $repeating_params = []; - $synopsis_spec = SynopsisParser::parse( $this->get_synopsis() ); - - // Build a set of assoc/flag canonical names (local + global) for conflict detection. - // Positional parameter names are excluded because an alias matching a positional - // name would not cause any real ambiguity (--alias vs bare positional). - $assoc_flag_names = []; - foreach ( $synopsis_spec as $param ) { - if ( in_array( $param['type'], [ 'assoc', 'flag' ], true ) ) { - $assoc_flag_names[] = $param['name']; - } - if ( 'assoc' === $param['type'] && ! empty( $param['repeating'] ) ) { - $repeating_params[ $param['name'] ] = true; - } + if ( \WP_CLI::get_config( 'prompt' ) && ! $prompted_once ) { + list( $_args, $assoc_args ) = $this->prompt_args( $args, $assoc_args ); + $args = array_merge( $args, $_args ); + $prompted_once = true; } - foreach ( SynopsisParser::parse( $this->get_global_params() ) as $param ) { - if ( in_array( $param['type'], [ 'assoc', 'flag' ], true ) ) { - $assoc_flag_names[] = $param['name']; - } - } - $assoc_flag_names = array_unique( $assoc_flag_names ); - - foreach ( $synopsis_spec as $param ) { - if ( empty( $param['aliases'] ) ) { - continue; - } - foreach ( $param['aliases'] as $alias ) { - // Detect duplicate aliases (same alias used for different params). - if ( isset( $aliases[ $alias ] ) && $aliases[ $alias ] !== $param['name'] ) { - WP_CLI::warning( - sprintf( - "Alias '%s' for parameter '%s' conflicts with existing alias for parameter '%s'. Skipping.", - $alias, - $param['name'], - $aliases[ $alias ] - ) - ); - continue; - } - - // Detect aliases that conflict with an assoc/flag canonical parameter name. - if ( in_array( $alias, $assoc_flag_names, true ) && $alias !== $param['name'] ) { - WP_CLI::warning( - sprintf( - "Alias '%s' for parameter '%s' conflicts with an existing parameter name. Skipping.", - $alias, - $param['name'] - ) - ); - continue; - } - - $aliases[ $alias ] = $param['name']; - } - } - if ( ! empty( $aliases ) ) { - WP_CLI::debug( 'Resolving argument aliases: ' . implode( ', ', array_keys( $aliases ) ), 'bootstrap' ); - } - $assoc_args = $this->resolve_arg_aliases( $assoc_args, $aliases, $repeating_params ); - $extra_args = $this->resolve_arg_aliases( $extra_args, $aliases, $repeating_params ); - - if ( 'help' !== $this->name ) { - if ( \WP_CLI::get_config( 'prompt' ) && ! $prompted_once ) { - list( $_args, $assoc_args ) = $this->prompt_args( $args, $assoc_args ); - $args = array_merge( $args, $_args ); - $prompted_once = true; - } - } - - $extra_positionals = []; + $extra_positionals = array(); foreach ( $extra_args as $k => $v ) { if ( is_numeric( $k ) ) { if ( ! isset( $args[ $k ] ) ) { @@ -746,69 +413,39 @@ public function invoke( $args, $assoc_args, $extra_args ) { unset( $assoc_args[ $key ] ); } - $path = get_path( $this->get_parent() ); + $path = get_path( $this->get_parent() ); $parent = implode( ' ', array_slice( $path, 1 ) ); - $cmd = $this->name; + $cmd = $this->name; if ( $parent ) { - WP_CLI::do_hook( "before_invoke:{$parent}", $parent ); + WP_CLI::do_hook( "before_invoke:{$parent}" ); $cmd = $parent . ' ' . $cmd; } - WP_CLI::do_hook( "before_invoke:{$cmd}", $cmd ); - - // Check if `--prompt` arg passed or not. - if ( $prompted_once ) { - // Unset empty args. - $actual_args = $assoc_args; - foreach ( $actual_args as $key => $value ) { - if ( empty( $value ) ) { - unset( $actual_args[ $key ] ); - } - } - - // Get list of sensitive arguments to mask in output - $sensitive_args = $this->get_sensitive_args(); - - WP_CLI::log( - sprintf( - 'wp %s %s', - $cmd, - ltrim( - implode( - ' ', - [ - ltrim( Utils\args_to_str( $args ), ' ' ), - ltrim( Utils\assoc_args_to_str( $actual_args, $sensitive_args ), ' ' ), - ] - ), - ' ' - ) - ) - ); - } + WP_CLI::do_hook( "before_invoke:{$cmd}" ); call_user_func( $this->when_invoked, $args, array_merge( $extra_args, $assoc_args ) ); if ( $parent ) { - WP_CLI::do_hook( "after_invoke:{$parent}", $parent ); + WP_CLI::do_hook( "after_invoke:{$parent}" ); } - WP_CLI::do_hook( "after_invoke:{$cmd}", $cmd ); + WP_CLI::do_hook( "after_invoke:{$cmd}" ); } /** * Get an array of parameter names, by merging the command-specific and the * global parameters. * - * @param array $spec Optional. Specification of the current command. + * @param array $spec Optional. Specification of the current command. * * @return array Array of parameter names */ - private function get_parameters( $spec = [] ) { - $local_parameters = array_column( $spec, 'name' ); + private function get_parameters( $spec = array() ) { + $local_parameters = array_column( $spec, 'name' ); $global_parameters = array_column( - SynopsisParser::parse( $this->get_global_params() ), + WP_CLI\SynopsisParser::parse( $this->get_global_params() ), 'name' ); return array_unique( array_merge( $local_parameters, $global_parameters ) ); } } + diff --git a/php/WP_CLI/DocParser.php b/php/WP_CLI/DocParser.php index 6139d4d1c..303f0b8e6 100644 --- a/php/WP_CLI/DocParser.php +++ b/php/WP_CLI/DocParser.php @@ -11,19 +11,17 @@ class DocParser { /** - * PHPdoc command for the command. - * - * @var string + * @var string $docComment PHPdoc command for the command. */ - protected $doc_comment; + protected $docComment; /** - * @param string $doc_comment + * @param string $docComment */ - public function __construct( $doc_comment ) { + public function __construct( $docComment ) { /* Make sure we have a known line ending in document */ - $doc_comment = str_replace( "\r\n", "\n", $doc_comment ); - $this->doc_comment = self::remove_decorations( $doc_comment ); + $docComment = str_replace( "\r\n", "\n", $docComment ); + $this->docComment = self::remove_decorations( $docComment ); } /** @@ -33,9 +31,9 @@ public function __construct( $doc_comment ) { * @return string */ private static function remove_decorations( $comment ) { - $comment = (string) preg_replace( '|^/\*\*[\r\n]+|', '', $comment ); - $comment = (string) preg_replace( '|\n[\t ]*\*/$|', '', $comment ); - $comment = (string) preg_replace( '|^[\t ]*\* ?|m', '', $comment ); + $comment = preg_replace( '|^/\*\*[\r\n]+|', '', $comment ); + $comment = preg_replace( '|\n[\t ]*\*/$|', '', $comment ); + $comment = preg_replace( '|^[\t ]*\* ?|m', '', $comment ); return $comment; } @@ -46,7 +44,7 @@ private static function remove_decorations( $comment ) { * @return string */ public function get_shortdesc() { - if ( ! preg_match( '|^([^@][^\n]+)\n*|', $this->doc_comment, $matches ) ) { + if ( ! preg_match( '|^([^@][^\n]+)\n*|', $this->docComment, $matches ) ) { return ''; } @@ -64,9 +62,9 @@ public function get_longdesc() { return ''; } - $longdesc = substr( $this->doc_comment, strlen( $shortdesc ) ); + $longdesc = substr( $this->docComment, strlen( $shortdesc ) ); - $lines = []; + $lines = array(); foreach ( explode( "\n", $longdesc ) as $line ) { if ( 0 === strpos( $line, '@' ) ) { break; @@ -74,8 +72,9 @@ public function get_longdesc() { $lines[] = $line; } + $longdesc = trim( implode( $lines, "\n" ) ); - return trim( implode( "\n", $lines ) ); + return $longdesc; } /** @@ -85,32 +84,20 @@ public function get_longdesc() { * @return string */ public function get_tag( $name ) { - if ( preg_match( '|^@' . $name . '\s+([a-z-_0-9]+)|m', $this->doc_comment, $matches ) ) { + if ( preg_match( '|^@' . $name . '\s+([a-z-_0-9]+)|m', $this->docComment, $matches ) ) { return $matches[1]; } return ''; } - /** - * Check if a given tag exists (e.g. "@skipglobalargcheck") - * - * Useful for checking the presence of valueless tags in PHPdoc. - * - * @param string $name Name for the tag, without '@' - * @return bool True if the tag exists, false otherwise. - */ - public function has_tag( $name ) { - return (bool) preg_match( '/^\s*\*?\s*@' . preg_quote( $name, '/' ) . '\b/m', $this->doc_comment ); - } - /** * Get the command's synopsis. * * @return string */ public function get_synopsis() { - if ( ! preg_match( '|^@synopsis\s+(.+)|m', $this->doc_comment, $matches ) ) { + if ( ! preg_match( '|^@synopsis\s+(.+)|m', $this->docComment, $matches ) ) { return ''; } @@ -125,18 +112,19 @@ public function get_synopsis() { */ public function get_arg_desc( $name ) { - if ( preg_match( "/\[?<{$name}>.+\n: (.+?)(\n|$)/", $this->doc_comment, $matches ) ) { + if ( preg_match( "/\[?<{$name}>.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { return $matches[1]; } return ''; + } /** * Get the arguments for a given argument. * * @param string $name Argument's doc name. - * @return array|null + * @return mixed|null */ public function get_arg_args( $name ) { return $this->get_arg_or_param_args( "/^\[?<{$name}>.*/" ); @@ -150,7 +138,7 @@ public function get_arg_args( $name ) { */ public function get_param_desc( $key ) { - if ( preg_match( "/\[?--{$key}=.+\n: (.+?)(\n|$)/", $this->doc_comment, $matches ) ) { + if ( preg_match( "/\[?--{$key}=.+\n: (.+?)(\n|$)/", $this->docComment, $matches ) ) { return $matches[1]; } @@ -161,7 +149,7 @@ public function get_param_desc( $key ) { * Get the arguments for a given parameter. * * @param string $key Parameter's key. - * @return array|null + * @return mixed|null */ public function get_param_args( $key ) { return $this->get_arg_or_param_args( "/^\[?--{$key}=.*/" ); @@ -174,10 +162,9 @@ public function get_param_args( $key ) { * @return array|null Interpreted YAML document, or null. */ private function get_arg_or_param_args( $regex ) { - $bits = explode( "\n", $this->doc_comment ); - $within_arg = false; - $within_doc = false; - $document = []; + $bits = explode( "\n", $this->docComment ); + $within_arg = $within_doc = false; + $document = array(); foreach ( $bits as $bit ) { if ( preg_match( $regex, $bit ) ) { $within_arg = true; @@ -206,4 +193,5 @@ private function get_arg_or_param_args( $regex ) { } return null; } + } diff --git a/php/WP_CLI/Exception/NonExistentKeyException.php b/php/WP_CLI/Exception/NonExistentKeyException.php deleted file mode 100644 index f4777bae7..000000000 --- a/php/WP_CLI/Exception/NonExistentKeyException.php +++ /dev/null @@ -1,28 +0,0 @@ - */ - protected $traverser; - - /** - * @param RecursiveDataStructureTraverser $traverser - */ - public function set_traverser( $traverser ) { - $this->traverser = $traverser; - } - - /** - * @return RecursiveDataStructureTraverser - */ - public function get_traverser() { - return $this->traverser; - } -} diff --git a/php/WP_CLI/ExitException.php b/php/WP_CLI/ExitException.php index d16871c75..8a86dc3e0 100644 --- a/php/WP_CLI/ExitException.php +++ b/php/WP_CLI/ExitException.php @@ -2,6 +2,4 @@ namespace WP_CLI; -use Exception; - -class ExitException extends Exception {} +class ExitException extends \Exception {} diff --git a/php/WP_CLI/Extractor.php b/php/WP_CLI/Extractor.php index 934863f39..4e1e9a66e 100644 --- a/php/WP_CLI/Extractor.php +++ b/php/WP_CLI/Extractor.php @@ -2,12 +2,12 @@ namespace WP_CLI; -use DirectoryIterator; use Exception; use PharData; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use WP_CLI; +use WP_CLI\Utils; use ZipArchive; /** @@ -28,8 +28,7 @@ public static function extract( $tarball_or_zip, $dest ) { if ( preg_match( '/\.tar\.gz$/', $tarball_or_zip ) ) { return self::extract_tarball( $tarball_or_zip, $dest ); } - - throw new Exception( "Extraction only supported for '.zip' and '.tar.gz' file types." ); + throw new \Exception( "Extraction only supported for '.zip' and '.tar.gz' file types." ); } /** @@ -40,46 +39,27 @@ public static function extract( $tarball_or_zip, $dest ) { */ private static function extract_zip( $zipfile, $dest ) { if ( ! class_exists( 'ZipArchive' ) ) { - throw new Exception( 'Extracting a zip file requires ZipArchive.' ); - } - - // Ensure the destination folder exists or can be created. - if ( ! self::ensure_dir_exists( $dest ) ) { - throw new Exception( "Could not create folder '{$dest}'." ); - } - - if ( ! file_exists( $zipfile ) - || ! is_readable( $zipfile ) - || filesize( $zipfile ) <= 0 ) { - throw new Exception( "Invalid zip file '{$zipfile}'." ); + throw new \Exception( 'Extracting a zip file requires ZipArchive.' ); } - $zip = new ZipArchive(); $res = $zip->open( $zipfile ); - if ( true === $res ) { - $name = Path::basename( $zipfile ); - $tempdir = Utils\get_temp_dir() - . uniqid( 'wp-cli-extract-zipfile-', true ) - . "-{$name}"; + $tempdir = implode( + DIRECTORY_SEPARATOR, + array( + dirname( $zipfile ), + Utils\basename( $zipfile, '.zip' ), + $zip->getNameIndex( 0 ), + ) + ); - $zip->extractTo( $tempdir ); + $zip->extractTo( dirname( $tempdir ) ); $zip->close(); - self::copy_overwrite_files( - self::get_first_subfolder( $tempdir ), - $dest - ); - - self::rmdir( $tempdir ); + self::copy_overwrite_files( $tempdir, $dest ); + self::rmdir( dirname( $tempdir ) ); } else { - throw new Exception( - sprintf( - "ZipArchive failed to unzip '%s': %s.", - $zipfile, - self::zip_error_msg( $res ) - ) - ); + throw new \Exception( sprintf( "ZipArchive failed to unzip '%s': %s.", $zipfile, self::zip_error_msg( $res ) ) ); } } @@ -90,113 +70,47 @@ private static function extract_zip( $zipfile, $dest ) { * @param string $dest */ private static function extract_tarball( $tarball, $dest ) { - // Ensure the destination folder exists or can be created. - if ( ! self::ensure_dir_exists( $dest ) ) { - throw new Exception( "Could not create folder '{$dest}'." ); - } - - $tarball_absolute = realpath( $tarball ); - if ( ! $tarball_absolute ) { - throw new Exception( "Invalid tarball '{$tarball}'." ); - } - $tarball = $tarball_absolute; - - if ( ! is_readable( $tarball ) - || filesize( $tarball ) <= 0 ) { - throw new Exception( "Invalid tarball '{$tarball}'." ); - } - - $tar_error = null; - - try { - // Note: directory must exist for tar --directory to work. - $force_local = Utils\is_windows() ? ' --force-local' : ''; - $cmd = Utils\esc_cmd( - "tar xz{$force_local} --strip-components=1 --directory=%s -f %s", - Path::normalize( $dest ), - Path::normalize( $tarball ) - ); - - $process_run = WP_CLI::launch( - $cmd, - false, /*exit_on_error*/ - true /*return_detailed*/ - ); - - if ( 0 === $process_run->return_code ) { - return; - } - - throw new Exception( (string) self::tar_error_msg( $process_run ) ); - } catch ( Exception $e ) { - $tar_error = $e->getMessage(); - if ( class_exists( 'PharData' ) ) { - WP_CLI::warning( - 'tar xz failed, falling back to PharData (' - . $tar_error . ')' - ); - } - } - - $phar_error = null; if ( class_exists( 'PharData' ) ) { - $name = Path::basename( $tarball ); - $tempdir = Utils\get_temp_dir() - . uniqid( 'wp-cli-extract-tarball-', true ) - . "-{$name}"; - try { $phar = new PharData( $tarball ); - $phar->extractTo( $tempdir ); - - self::copy_overwrite_files( - self::get_first_subfolder( $tempdir ), - $dest + $tempdir = implode( + DIRECTORY_SEPARATOR, + array( + dirname( $tarball ), + Utils\basename( $tarball, '.tar.gz' ), + $phar->getFilename(), + ) ); + + $phar->extractTo( dirname( $tempdir ), null, true ); + + self::copy_overwrite_files( $tempdir, $dest ); + + self::rmdir( dirname( $tempdir ) ); return; - } catch ( Exception $e ) { - $phar_error = $e->getMessage(); - } finally { - if ( is_dir( $tempdir ) ) { - try { - self::rmdir( $tempdir ); - } catch ( Exception $e ) { - // Ignore cleanup errors to avoid masking primary exceptions. - unset( $e ); - } - } + } catch ( \Exception $e ) { + WP_CLI::warning( "PharData failed, falling back to 'tar xz' (" . $e->getMessage() . ')' ); + // Fall through to trying `tar xz` below } } - - $errors = []; - if ( $tar_error ) { - $errors[] = "tar xz failed: {$tar_error}"; - } - if ( $phar_error ) { - $errors[] = "PharData failed: {$phar_error}"; + // Note: directory must exist for tar --directory to work. + $cmd = Utils\esc_cmd( 'tar xz --strip-components=1 --directory=%s -f %s', $dest, $tarball ); + $process_run = WP_CLI::launch( $cmd, false /*exit_on_error*/, true /*return_detailed*/ ); + if ( 0 !== $process_run->return_code ) { + throw new \Exception( sprintf( 'Failed to execute `%s`: %s.', $cmd, self::tar_error_msg( $process_run ) ) ); } - - if ( empty( $errors ) ) { - throw new Exception( 'Failed to extract the tarball.' ); - } - - throw new Exception( 'Failed to extract the tarball. ' . implode( ' ', $errors ) ); } /** - * Copy files from source directory to destination directory. Source - * directory must exist. + * Copy files from source directory to destination directory. Source directory must exist. * * @param string $source * @param string $dest */ public static function copy_overwrite_files( $source, $dest ) { $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( - $source, - RecursiveDirectoryIterator::SKIP_DOTS - ), + new RecursiveDirectoryIterator( $source, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::SELF_FIRST ); @@ -206,69 +120,45 @@ public static function copy_overwrite_files( $source, $dest ) { mkdir( $dest, 0777, true ); } - /** - * @var \SplFileInfo $item - */ foreach ( $iterator as $item ) { - $dest_path = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathname(); + $dest_path = $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); if ( $item->isDir() ) { if ( ! is_dir( $dest_path ) ) { mkdir( $dest_path ); } - } elseif ( file_exists( $dest_path ) && is_writable( $dest_path ) ) { - copy( $item, $dest_path ); - } elseif ( ! file_exists( $dest_path ) ) { - copy( $item, $dest_path ); } else { - $error = 1; - WP_CLI::warning( "Unable to copy '" . $iterator->getSubPathname() . "' to current directory." ); + if ( file_exists( $dest_path ) && is_writable( $dest_path ) ) { + copy( $item, $dest_path ); + } elseif ( ! file_exists( $dest_path ) ) { + copy( $item, $dest_path ); + } else { + $error = 1; + WP_CLI::warning( "Unable to copy '" . $iterator->getSubPathName() . "' to current directory." ); + } } } if ( $error ) { - throw new Exception( 'There was an error overwriting existing files.' ); + throw new \Exception( 'There was an error overwriting existing files.' ); } } /** - * Delete all files and directories recursively from directory. Directory - * must exist. + * Delete all files and directories recursively from directory. Directory must exist. * * @param string $dir */ public static function rmdir( $dir ) { $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( - $dir, - RecursiveDirectoryIterator::SKIP_DOTS - ), + new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ); - $base_dir = realpath( $dir ); - if ( false === $base_dir ) { - return; - } - $base_dir = rtrim( $base_dir, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR; - - /** - * @var \SplFileInfo $fileinfo - */ foreach ( $files as $fileinfo ) { - $todo = $fileinfo->isDir() ? 'rmdir' : 'unlink'; - $path = $fileinfo->getPathname(); - $real_path = $fileinfo->getRealPath(); - - if ( ! $real_path || 0 !== strpos( $real_path, $base_dir ) ) { - WP_CLI::warning( - "Temporary file or folder to be removed was found outside of temporary folder, aborting removal: '{$path}'" - ); - continue; - } - - $todo( $path ); + $todo = $fileinfo->isDir() ? 'rmdir' : 'unlink'; + $todo( $fileinfo->getRealPath() ); } rmdir( $dir ); } @@ -277,45 +167,39 @@ public static function rmdir( $dir ) { * Return formatted ZipArchive error message from error code. * * @param int $error_code - * @return string|int The error message corresponding to the specified - * code, if found; Other wise the same error code, - * unmodified. + * @return string */ public static function zip_error_msg( $error_code ) { - // From https://github.com/php/php-src/blob/php-5.3.0/ext/zip/php_zip.c#L2623-L2646. - static $zip_err_msgs = [ - ZipArchive::ER_OK => 'No error', - ZipArchive::ER_MULTIDISK => 'Multi-disk zip archives not supported', - ZipArchive::ER_RENAME => 'Renaming temporary file failed', - ZipArchive::ER_CLOSE => 'Closing zip archive failed', - ZipArchive::ER_SEEK => 'Seek error', - ZipArchive::ER_READ => 'Read error', - ZipArchive::ER_WRITE => 'Write error', - ZipArchive::ER_CRC => 'CRC error', - ZipArchive::ER_ZIPCLOSED => 'Containing zip archive was closed', - ZipArchive::ER_NOENT => 'No such file', - ZipArchive::ER_EXISTS => 'File already exists', - ZipArchive::ER_OPEN => 'Can\'t open file', - ZipArchive::ER_TMPOPEN => 'Failure to create temporary file', - ZipArchive::ER_ZLIB => 'Zlib error', - ZipArchive::ER_MEMORY => 'Malloc failure', - ZipArchive::ER_CHANGED => 'Entry has been changed', + // From https://github.com/php/php-src/blob/php-5.3.0/ext/zip/php_zip.c#L2623-L2646 + static $zip_err_msgs = array( + ZipArchive::ER_OK => 'No error', + ZipArchive::ER_MULTIDISK => 'Multi-disk zip archives not supported', + ZipArchive::ER_RENAME => 'Renaming temporary file failed', + ZipArchive::ER_CLOSE => 'Closing zip archive failed', + ZipArchive::ER_SEEK => 'Seek error', + ZipArchive::ER_READ => 'Read error', + ZipArchive::ER_WRITE => 'Write error', + ZipArchive::ER_CRC => 'CRC error', + ZipArchive::ER_ZIPCLOSED => 'Containing zip archive was closed', + ZipArchive::ER_NOENT => 'No such file', + ZipArchive::ER_EXISTS => 'File already exists', + ZipArchive::ER_OPEN => 'Can\'t open file', + ZipArchive::ER_TMPOPEN => 'Failure to create temporary file', + ZipArchive::ER_ZLIB => 'Zlib error', + ZipArchive::ER_MEMORY => 'Malloc failure', + ZipArchive::ER_CHANGED => 'Entry has been changed', ZipArchive::ER_COMPNOTSUPP => 'Compression method not supported', - ZipArchive::ER_EOF => 'Premature EOF', - ZipArchive::ER_INVAL => 'Invalid argument', - ZipArchive::ER_NOZIP => 'Not a zip archive', - ZipArchive::ER_INTERNAL => 'Internal error', - ZipArchive::ER_INCONS => 'Zip archive inconsistent', - ZipArchive::ER_REMOVE => 'Can\'t remove file', - ZipArchive::ER_DELETED => 'Entry has been deleted', - ]; + ZipArchive::ER_EOF => 'Premature EOF', + ZipArchive::ER_INVAL => 'Invalid argument', + ZipArchive::ER_NOZIP => 'Not a zip archive', + ZipArchive::ER_INTERNAL => 'Internal error', + ZipArchive::ER_INCONS => 'Zip archive inconsistent', + ZipArchive::ER_REMOVE => 'Can\'t remove file', + ZipArchive::ER_DELETED => 'Entry has been deleted', + ); if ( isset( $zip_err_msgs[ $error_code ] ) ) { - return sprintf( - '%s (%d)', - $zip_err_msgs[ $error_code ], - $error_code - ); + return sprintf( '%s (%d)', $zip_err_msgs[ $error_code ], $error_code ); } return $error_code; } @@ -323,14 +207,12 @@ public static function zip_error_msg( $error_code ) { /** * Return formatted error message from ProcessRun of tar command. * - * @param ProcessRun $process_run - * @return string|int The error message of the process, if available; - * otherwise the return code. + * @param Processrun $process_run + * @return string */ public static function tar_error_msg( $process_run ) { $stderr = trim( $process_run->stderr ); - $nl_pos = strpos( $stderr, "\n" ); - if ( false !== $nl_pos ) { + if ( false !== ( $nl_pos = strpos( $stderr, "\n" ) ) ) { $stderr = trim( substr( $stderr, 0, $nl_pos ) ); } if ( $stderr ) { @@ -338,48 +220,4 @@ public static function tar_error_msg( $process_run ) { } return $process_run->return_code; } - - /** - * Return the first subfolder within a given path. - * - * Falls back to the provided path if no subfolder was detected. - * - * @param string $path Path to find the first subfolder in. - * @return string First subfolder, or same as $path if none found. - */ - private static function get_first_subfolder( $path ) { - $iterator = new DirectoryIterator( $path ); - - foreach ( $iterator as $fileinfo ) { - if ( $fileinfo->isDir() && ! $fileinfo->isDot() ) { - return "{$path}/{$fileinfo->getFilename()}"; - } - } - - return $path; - } - - /** - * Ensure directory exists. - * - * @param string $dir Directory to ensure the existence of. - * @return bool Whether the existence could be asserted. - */ - private static function ensure_dir_exists( $dir ) { - if ( ! is_dir( $dir ) ) { - if ( ! @mkdir( $dir, 0777, true ) ) { - $error = error_get_last(); - WP_CLI::warning( - sprintf( - "Failed to create directory '%s': %s.", - $dir, - $error ? $error['message'] : 'Unknown error' - ) - ); - return false; - } - } - - return true; - } } diff --git a/php/WP_CLI/Fetchers/Base.php b/php/WP_CLI/Fetchers/Base.php index 06c55b2f6..639efe103 100644 --- a/php/WP_CLI/Fetchers/Base.php +++ b/php/WP_CLI/Fetchers/Base.php @@ -2,56 +2,43 @@ namespace WP_CLI\Fetchers; -use WP_CLI; -use WP_CLI\ExitException; - /** * Fetch a WordPress entity for use in a subcommand. - * - * @template T */ abstract class Base { /** - * The message to display when an item is not found. - * - * @var string + * @var string $msg The message to display when an item is not found */ protected $msg; /** - * @param string|int $arg The raw CLI argument. - * @return T|false The item if found; false otherwise. + * @param string $arg The raw CLI argument + * @return mixed|false The item if found; false otherwise */ abstract public function get( $arg ); /** * Like get(), but calls WP_CLI::error() instead of returning false. * - * @param string $arg The raw CLI argument. - * @return T The item if found. - * @throws ExitException If the item is not found. - * - * @phpstan-assert-if-true !false $this->get() + * @param string $arg The raw CLI argument */ public function get_check( $arg ) { $item = $this->get( $arg ); if ( ! $item ) { - WP_CLI::error( sprintf( $this->msg, $arg ) ); + \WP_CLI::error( sprintf( $this->msg, $arg ) ); } return $item; } /** - * Get multiple items. - * - * @param array $args The raw CLI arguments. - * @return T[] The list of found items. + * @param array The raw CLI arguments + * @return array The list of found items */ public function get_many( $args ) { - $items = []; + $items = array(); foreach ( $args as $arg ) { $item = $this->get( $arg ); @@ -59,10 +46,11 @@ public function get_many( $args ) { if ( $item ) { $items[] = $item; } else { - WP_CLI::warning( sprintf( $this->msg, $arg ) ); + \WP_CLI::warning( sprintf( $this->msg, $arg ) ); } } return $items; } } + diff --git a/php/WP_CLI/Fetchers/Comment.php b/php/WP_CLI/Fetchers/Comment.php deleted file mode 100644 index d5ed08d2a..000000000 --- a/php/WP_CLI/Fetchers/Comment.php +++ /dev/null @@ -1,37 +0,0 @@ - - */ -class Comment extends Base { - - /** - * The message to display when an item is not found. - * - * @var string - */ - protected $msg = 'Could not find the comment with ID %d.'; - - /** - * Get a comment object by ID - * - * @param string|int $arg The raw CLI argument. - * @return WP_Comment|false The item if found; false otherwise. - */ - public function get( $arg ) { - $comment_id = (int) $arg; - $comment = get_comment( $comment_id ); - - if ( null === $comment ) { - return false; - } - - return $comment; - } -} diff --git a/php/WP_CLI/Fetchers/Post.php b/php/WP_CLI/Fetchers/Post.php deleted file mode 100644 index 339d9b009..000000000 --- a/php/WP_CLI/Fetchers/Post.php +++ /dev/null @@ -1,39 +0,0 @@ - - */ -class Post extends Base { - - /** - * The message to display when an item is not found. - * - * @var string - */ - protected $msg = 'Could not find the post with ID %d.'; - - /** - * Get a post object by ID - * - * @param string|int $arg The raw CLI argument. - * @return WP_Post|false The item if found; false otherwise. - */ - public function get( $arg ) { - /** - * @var WP_Post|null $post - */ - $post = get_post( (int) $arg ); - - if ( null === $post ) { - return false; - } - - return $post; - } -} diff --git a/php/WP_CLI/Fetchers/Signup.php b/php/WP_CLI/Fetchers/Signup.php deleted file mode 100644 index 85d7b2bbc..000000000 --- a/php/WP_CLI/Fetchers/Signup.php +++ /dev/null @@ -1,68 +0,0 @@ - - */ -class Signup extends Base { - - /** - * The message to display when an item is not found. - * - * @var string - */ - protected $msg = "Invalid signup ID, email, login, or activation key: '%s'"; - - /** - * Get a signup. - * - * @param int|string $signup - * @return object|false - */ - public function get( $signup ) { - return $this->get_signup( $signup ); - } - - /** - * Get a signup by one of its identifying attributes. - * - * @param int|string $arg The raw CLI argument. - * @return object|false The item if found; false otherwise. - */ - protected function get_signup( $arg ) { - global $wpdb; - - $signup_object = null; - - // Fetch signup with signup_id. - if ( is_numeric( $arg ) ) { - $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->signups WHERE signup_id = %d", $arg ) ); - - if ( $result ) { - $signup_object = $result; - } - } - - if ( ! $signup_object ) { - // Try to fetch with other keys. - foreach ( array( 'user_login', 'user_email', 'activation_key' ) as $field ) { - // phpcs:ignore WordPress.DB.PreparedSQL - $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->signups WHERE $field = %s", $arg ) ); - - if ( $result ) { - $signup_object = $result; - break; - } - } - } - - if ( $signup_object ) { - return $signup_object; - } - - return false; - } -} diff --git a/php/WP_CLI/Fetchers/Site.php b/php/WP_CLI/Fetchers/Site.php deleted file mode 100644 index 54d2ecbd4..000000000 --- a/php/WP_CLI/Fetchers/Site.php +++ /dev/null @@ -1,61 +0,0 @@ - - */ -class Site extends Base { - - /** - * The message to display when an item is not found. - * - * @var string - */ - protected $msg = 'Could not find the site with ID %d.'; - - /** - * Get a site object by ID - * - * @param string|int $site_id - * @return object|false - * - * @phpstan-return SiteObject|false - */ - public function get( $site_id ) { - return $this->get_site( (int) $site_id ); - } - - /** - * Get site (blog) data for a given id. - * - * @global wpdb $wpdb WordPress database abstraction object. - * - * @param string|int $arg The raw CLI argument. - * @return object|false The item if found; false otherwise. - * - * @phpstan-return SiteObject|false - */ - private function get_site( $arg ) { - global $wpdb; - - // Load site data - $site = $wpdb->get_row( - $wpdb->prepare( - "SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d", - $arg - ) - ); - - if ( ! empty( $site ) ) { - // Only care about domain and path which are set here - return $site; - } - - return false; - } -} diff --git a/php/WP_CLI/Fetchers/User.php b/php/WP_CLI/Fetchers/User.php deleted file mode 100644 index fb41ed76d..000000000 --- a/php/WP_CLI/Fetchers/User.php +++ /dev/null @@ -1,58 +0,0 @@ - - */ -class User extends Base { - - /** - * The message to display when an item is not found. - * - * @var string - */ - protected $msg = "Invalid user ID, email or login: '%s'"; - - /** - * Get a user object by one of its identifying attributes. - * - * @param string|int $arg The raw CLI argument. - * @return WP_User|false The item if found; false otherwise. - */ - public function get( $arg ) { - - if ( getenv( 'WP_CLI_FORCE_USER_LOGIN' ) ) { - $this->msg = "Invalid user login: '%s'"; - return get_user_by( 'login', $arg ); - } - - if ( is_numeric( $arg ) ) { - $check = get_user_by( 'login', $arg ); - $user = get_user_by( 'id', $arg ); - if ( $check && $user ) { - WP_CLI::warning( - sprintf( - 'Ambiguous user match detected (both ID and user_login exist for identifier \'%d\'). WP-CLI will default to the ID, but you can force user_login instead with WP_CLI_FORCE_USER_LOGIN=1.', - $arg - ) - ); - } - } elseif ( is_email( $arg ) ) { - $user = get_user_by( 'email', $arg ); - // Logins can be emails. - if ( ! $user ) { - $user = get_user_by( 'login', $arg ); - } - } else { - $user = get_user_by( 'login', $arg ); - } - - return $user; - } -} diff --git a/php/WP_CLI/FileCache.php b/php/WP_CLI/FileCache.php index bc6ffd0dd..ff7da5214 100644 --- a/php/WP_CLI/FileCache.php +++ b/php/WP_CLI/FileCache.php @@ -13,13 +13,7 @@ namespace WP_CLI; -use DateTime; -use Exception; -use FilesystemIterator; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; -use SplFileInfo; -use WP_CLI; +use Symfony\Component\Finder\Finder; /** * Reads/writes to a filesystem cache @@ -41,27 +35,28 @@ class FileCache { /** * @var int max total size */ - protected $max_size; + protected $maxSize; /** * @var string key allowed chars (regex class) */ protected $whitelist; /** - * @param string $cache_dir location of the cache + * @param string $cacheDir location of the cache * @param int $ttl cache files default time to live (expiration) - * @param int $max_size max total cache size + * @param int $maxSize max total cache size * @param string $whitelist List of characters that are allowed in path names (used in a regex character class) */ - public function __construct( $cache_dir, $ttl, $max_size, $whitelist = 'a-z0-9._-' ) { - $this->root = Path::trailingslashit( $cache_dir ); - $this->ttl = (int) $ttl; - $this->max_size = (int) $max_size; + public function __construct( $cacheDir, $ttl, $maxSize, $whitelist = 'a-z0-9._-' ) { + $this->root = Utils\trailingslashit( $cacheDir ); + $this->ttl = (int) $ttl; + $this->maxSize = (int) $maxSize; $this->whitelist = $whitelist; if ( ! $this->ensure_dir_exists( $this->root ) ) { $this->enabled = false; } + } /** @@ -88,9 +83,7 @@ public function get_root() { * * @param string $key cache key * @param int $ttl time to live - * @return false|string filename or false - * - * @phpstan-assert-if-true string $this->read() + * @return bool|string filename or false */ public function has( $key, $ttl = null ) { if ( ! $this->enabled ) { @@ -103,7 +96,7 @@ public function has( $key, $ttl = null ) { return false; } - // Use ttl param or global ttl. + // use ttl param or global ttl if ( null === $ttl ) { $ttl = $this->ttl; } elseif ( $this->ttl > 0 ) { @@ -112,12 +105,8 @@ public function has( $key, $ttl = null ) { $ttl = (int) $ttl; } - $modified_time = filemtime( $filename ); - if ( false === $modified_time ) { - $modified_time = 0; - } - - if ( $ttl > 0 && ( $modified_time + $ttl ) < time() ) { + // + if ( $ttl > 0 && filemtime( $filename ) + $ttl < time() ) { if ( $this->ttl > 0 && $ttl >= $this->ttl ) { unlink( $filename ); } @@ -149,13 +138,13 @@ public function write( $key, $contents ) { * * @param string $key cache key * @param int $ttl time to live - * @return false|string file contents or false + * @return bool|string file contents or false */ public function read( $key, $ttl = null ) { $filename = $this->has( $key, $ttl ); if ( $filename ) { - return (string) file_get_contents( $filename ); + return file_get_contents( $filename ); } return false; @@ -165,16 +154,12 @@ public function read( $key, $ttl = null ) { * Copy a file into the cache * * @param string $key cache key - * @param string $source source filename; tmp file filepath from HTTP response + * @param string $source source filename * @return bool */ public function import( $key, $source ) { $filename = $this->prepare_write( $key ); - if ( ! is_readable( $source ) ) { - return false; - } - if ( $filename ) { return copy( $source, $filename ) && touch( $filename ); } @@ -193,7 +178,7 @@ public function import( $key, $source ) { public function export( $key, $target, $ttl = null ) { $filename = $this->has( $key, $ttl ); - if ( $filename && $this->ensure_dir_exists( dirname( $target ) ) ) { + if ( $filename ) { return copy( $filename, $target ); } @@ -230,43 +215,31 @@ public function clean() { return false; } - $ttl = $this->ttl; - $max_size = $this->max_size; + $ttl = $this->ttl; + $maxSize = $this->maxSize; - // Unlink expired files. + // unlink expired files if ( $ttl > 0 ) { try { - $expire = new DateTime(); - $expire->modify( '-' . $ttl . ' seconds' ); - $expire_time = $expire->getTimestamp(); - - $files = $this->get_cache_files(); - foreach ( $files as $file ) { - if ( $file->getMTime() <= $expire_time ) { - unlink( $file->getRealPath() ); - } - } - } catch ( Exception $e ) { - WP_CLI::error( $e->getMessage() ); + $expire = new \DateTime(); + } catch ( \Exception $e ) { + \WP_CLI::error( $e->getMessage() ); } - } - - // Unlink older files if max cache size is exceeded. - if ( $max_size > 0 ) { - $files = $this->get_cache_files(); + $expire->modify( '-' . $ttl . ' seconds' ); - // Sort files by accessed time (newest first) - usort( - $files, - static function ( $a, $b ) { - return $b->getATime() <=> $a->getATime(); - } - ); + $finder = $this->get_finder()->date( 'until ' . $expire->format( 'Y-m-d H:i:s' ) ); + foreach ( $finder as $file ) { + unlink( $file->getRealPath() ); + } + } + // unlink older files if max cache size is exceeded + if ( $maxSize > 0 ) { + $files = array_reverse( iterator_to_array( $this->get_finder()->sortByAccessedTime()->getIterator() ) ); $total = 0; foreach ( $files as $file ) { - if ( ( $total + $file->getSize() ) <= $max_size ) { + if ( $total + $file->getSize() <= $maxSize ) { $total += $file->getSize(); } else { unlink( $file->getRealPath() ); @@ -277,112 +250,6 @@ static function ( $a, $b ) { return true; } - /** - * Remove all cached files. - * - * @return bool - */ - public function clear() { - if ( ! $this->enabled ) { - return false; - } - - $files = $this->get_cache_files(); - - foreach ( $files as $file ) { - unlink( $file->getRealPath() ); - } - - return true; - } - - /** - * Remove all cached files except for the newest version of one. - * - * @return bool - */ - public function prune() { - if ( ! $this->enabled ) { - return false; - } - - $cache_files = $this->get_cache_files(); - - // Sort files by name - usort( - $cache_files, - static function ( $a, $b ) { - return strcmp( $a->getFilename(), $b->getFilename() ); - } - ); - - $files_by_base = []; - - // Group files by their base name (stripping version/timestamp). - foreach ( $cache_files as $file ) { - $basename = $file->getBasename(); - $pieces = explode( '-', $file->getBasename( $file->getExtension() ) ); - $last_piece = end( $pieces ); - - // Try to identify a version or timestamp suffix. - $basename_without_suffix = $basename; - $version_string = null; - - // Check if last piece is purely numeric (original timestamp format). - if ( is_numeric( $last_piece ) ) { - $basename_without_suffix = str_replace( '-' . $last_piece, '', $basename ); - $version_string = $last_piece; // Store as string for comparison. - } elseif ( preg_match( '/^(\d+(?:\.\d+)*)/', $last_piece, $matches ) ) { - // Handle version numbers like "8.6.1" in "jetpack-8.6.1.zip". - $basename_without_suffix = str_replace( '-' . $last_piece, '', $basename ); - $version_string = $matches[0]; // Store the version string. - } - - // Store file info: path, modification time, and optional version string. - if ( ! isset( $files_by_base[ $basename_without_suffix ] ) ) { - $files_by_base[ $basename_without_suffix ] = []; - } - - $files_by_base[ $basename_without_suffix ][] = [ - 'path' => $file->getRealPath(), - 'mtime' => $file->getMTime(), - 'version' => $version_string, - ]; - } - - // For each group, keep only the newest file and delete the rest. - foreach ( $files_by_base as $files ) { - if ( count( $files ) <= 1 ) { - continue; - } - - // Sort files: prefer version comparison if available, otherwise use mtime. - usort( - $files, - static function ( $a, $b ) { - // If both have version strings, use version_compare(). - if ( null !== $a['version'] && null !== $b['version'] ) { - $cmp = version_compare( $b['version'], $a['version'] ); - if ( 0 !== $cmp ) { - return $cmp; - } - // If versions are equal, fall through to mtime comparison. - } - // Otherwise, compare by modification time. - return $b['mtime'] <=> $a['mtime']; - } - ); - - // Delete all except the first (newest). - $total = count( $files ); - for ( $i = 1; $i < $total; $i++ ) { - unlink( $files[ $i ]['path'] ); - } - } - - return true; - } - /** * Ensure directory exists * @@ -391,18 +258,9 @@ static function ( $a, $b ) { */ protected function ensure_dir_exists( $dir ) { if ( ! is_dir( $dir ) ) { - // Disable the cache if a null device like /dev/null is being used. - if ( preg_match( '{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $dir ) ) { - return false; - } - - if ( ! @mkdir( $dir, 0777, true ) ) { - $message = "Failed to create directory '{$dir}'"; - $error = error_get_last(); - if ( is_array( $error ) ) { - $message .= ": {$error['message']}"; - } - WP_CLI::warning( "{$message}." ); + if ( ! @mkdir( $dir, 0777, true ) ) { // @codingStandardsIgnoreLine + $error = error_get_last(); + \WP_CLI::warning( sprintf( "Failed to create directory '%s': %s.", $dir, $error['message'] ) ); return false; } } @@ -414,7 +272,7 @@ protected function ensure_dir_exists( $dir ) { * Prepare cache write * * @param string $key cache key - * @return false|string The destination filename or false when cache disabled or directory creation fails. + * @return bool|string filename or false */ protected function prepare_write( $key ) { if ( ! $this->enabled ) { @@ -437,36 +295,25 @@ protected function prepare_write( $key ) { * @return string relative filename */ protected function validate_key( $key ) { - $url_parts = Utils\parse_url( $key, -1, false ); - if ( $url_parts && array_key_exists( 'path', $url_parts ) && ! empty( $url_parts['scheme'] ) ) { // is url - $parts = [ 'misc' ]; - $parts[] = $url_parts['scheme'] . - ( empty( $url_parts['host'] ) ? '' : '-' . $url_parts['host'] ) . + $url_parts = parse_url( $key ); + if ( ! empty( $url_parts['scheme'] ) ) { // is url + $parts = array( 'misc' ); + $parts[] = $url_parts['scheme'] . '-' . $url_parts['host'] . ( empty( $url_parts['port'] ) ? '' : '-' . $url_parts['port'] ); - $path_parts = explode( '/', substr( $url_parts['path'], 1 ) ); - if ( ! empty( $url_parts['query'] ) ) { - $path_parts[ count( $path_parts ) - 1 ] .= '-' . $url_parts['query']; - } - $parts = array_merge( $parts, $path_parts ); + $parts[] = substr( $url_parts['path'], 1 ) . + ( empty( $url_parts['query'] ) ? '' : '-' . $url_parts['query'] ); } else { - $key = str_replace( '\\', '/', $key ); + $key = str_replace( '\\', '/', $key ); $parts = explode( '/', ltrim( $key ) ); } $parts = preg_replace( "#[^{$this->whitelist}]#i", '-', $parts ); - foreach ( $parts as &$part ) { - if ( '..' === $part || '.' === $part ) { - $part = '-'; - } - } - unset( $part ); - - return rtrim( implode( '/', $parts ), '.' ); + return implode( '/', $parts ); } /** - * Destination filename from key + * Filename from key * * @param string $key * @return string filename @@ -476,42 +323,11 @@ protected function filename( $key ) { } /** - * Get all files in the cache directory recursively + * Get a Finder that iterates in cache root only the files * - * @return SplFileInfo[] + * @return Finder */ - protected function get_cache_files() { - $files = []; - - if ( ! is_dir( $this->root ) ) { - return $files; - } - - try { - // Match Symfony Finder behavior: do not follow symlinks. - // We explicitly do NOT include FilesystemIterator::FOLLOW_SYMLINKS flag. - // This prevents the iterator from traversing into symlinked directories. - // We also filter out symlink files themselves with !isLink() check. - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( - $this->root, - FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS - ), - RecursiveIteratorIterator::LEAVES_ONLY - ); - - foreach ( $iterator as $file ) { - if ( $file instanceof SplFileInfo && $file->isFile() && ! $file->isLink() ) { - $files[] = $file; - } - } - } catch ( Exception $e ) { - // If directory iteration fails (e.g., permissions issue, directory deleted), - // return empty array. This matches the behavior of Symfony Finder which - // would also return an empty result for inaccessible directories. - return []; - } - - return $files; + protected function get_finder() { + return Finder::create()->in( $this->root )->files(); } } diff --git a/php/WP_CLI/Formatter.php b/php/WP_CLI/Formatter.php index 635d0939c..21707b15e 100644 --- a/php/WP_CLI/Formatter.php +++ b/php/WP_CLI/Formatter.php @@ -2,83 +2,36 @@ namespace WP_CLI; -use cli\Colors; -use cli\Table; -use Iterator; use Mustangostang\Spyc; -use WP_CLI; /** * Output one or more items in a given format (e.g. table, JSON). - * - * Supports built-in formats (table, json, csv, yaml, count, ids) and allows - * extensions to register custom formats via Formatter::add_format(). - * - * @property-read string $format - * @property-read string[] $fields - * @property-read string|null $field - * @property-read array $alignments */ class Formatter { /** - * Maximum width for a table cell value. - * Values longer than this will be truncated to improve performance. - * - * @var int - */ - const MAX_CELL_WIDTH = 2048; - - /** - * Custom format handlers registered by extensions. - * - * @var array - */ - private static $custom_formatters = []; - - /** - * Options for custom format handlers. - * - * @var array - */ - private static $format_options = []; - - /** - * Single-value format handlers for WP_CLI::print_value(). - * - * @var array - */ - private static $single_value_formatters = []; - - /** - * How the items should be output. - * - * @var array{format: string, fields: string[], field: string|null, alignments: array} + * @var array $args How the items should be output. */ private $args; /** - * Standard prefix for object fields. - * - * @var string|false + * @var string $prefix Standard prefix for object fields. */ private $prefix; /** * @param array $assoc_args Output format arguments. * @param array $fields Fields to display of each item. - * @param string|false $prefix Check if fields have a standard prefix. - * False indicates empty prefix. + * @param string $prefix Check if fields have a standard prefix. */ public function __construct( &$assoc_args, $fields = null, $prefix = false ) { - $format_args = [ - 'format' => 'table', - 'fields' => $fields, - 'field' => null, - 'alignments' => [], - ]; - - foreach ( array_keys( $format_args ) as $key ) { + $format_args = array( + 'format' => 'table', + 'fields' => $fields, + 'field' => null, + ); + + foreach ( array( 'format', 'fields', 'field' ) as $key ) { if ( isset( $assoc_args[ $key ] ) ) { $format_args[ $key ] = $assoc_args[ $key ]; unset( $assoc_args[ $key ] ); @@ -89,268 +42,12 @@ public function __construct( &$assoc_args, $fields = null, $prefix = false ) { $format_args['fields'] = explode( ',', $format_args['fields'] ); } - /** @var callable(string): string $trim */ - $trim = 'trim'; - // @phpstan-ignore argument.type - $format_args['fields'] = array_map( $trim, $format_args['fields'] ); + $format_args['fields'] = array_map( 'trim', $format_args['fields'] ); - $this->args = $format_args; + $this->args = $format_args; $this->prefix = $prefix; } - /** - * Register a custom format handler. - * - * Allows extensions to add custom output formats. The handler receives an array - * of items (each item is an array of field => value pairs), an array of field - * names, the Formatter instance, and a key/value args array, and should output - * the formatted data directly. - * - * Built-in formats can be overridden by registering a handler with the same name. - * - * ## EXAMPLE - * - * // Register a custom XML format - * WP_CLI\Formatter::add_format( 'xml', function( $items, $fields, $formatter, $args ) { - * echo "\n\n"; - * foreach ( $items as $item ) { - * echo " \n"; - * foreach ( $item as $key => $value ) { - * echo " <{$key}>" . htmlspecialchars( $value ) . "\n"; - * } - * echo " \n"; - * } - * echo "\n"; - * }); - * - * @param string $format_name Name of the format (e.g. 'xml', 'nagios'). - * @param callable $handler Callback to handle formatting. Receives ($items, $fields, $formatter, $args) and should output directly. - * @param array{single_item?: bool} $options Optional metadata/options. - */ - public static function add_format( $format_name, $handler, $options = [] ) { - if ( ! is_callable( $handler ) ) { - WP_CLI::error( 'Format handler must be callable.' ); - } - self::$custom_formatters[ $format_name ] = $handler; - self::$format_options[ $format_name ] = $options; - } - - - - /** - * Register a custom single-value format handler for WP_CLI::print_value(). - * - * Allows extensions to add custom output formats for single values. The handler - * receives a single value and should return the formatted string (without trailing newline). - * - * Built-in single-value formats can be overridden by registering a handler with the same name. - * - * ## EXAMPLE - * - * // Register a custom format for single values - * WP_CLI\Formatter::add_single_value_format( 'plaintext', function( $value ) { - * if ( is_array( $value ) || is_object( $value ) ) { - * return var_export( $value, true ); - * } - * return (string) $value; - * }); - * - * @param string $format_name Name of the format (e.g. 'json', 'yaml', 'plaintext'). - * @param callable $handler Callback to handle formatting. Receives ($value) and should return formatted string. - */ - public static function add_single_value_format( $format_name, $handler ) { - if ( ! is_callable( $handler ) ) { - WP_CLI::error( 'Single-value format handler must be callable.' ); - } - self::$single_value_formatters[ $format_name ] = $handler; - } - - /** - * Format a single value using registered formatters. - * - * Used by WP_CLI::print_value() to format single values. - * - * @param mixed $value The value to format. - * @param string $format The format to use (e.g. 'json', 'yaml', 'var_export'). - * @return string The formatted value (without trailing newline). - */ - public static function format_single_value( $value, $format ) { - if ( isset( self::$single_value_formatters[ $format ] ) ) { - return call_user_func( self::$single_value_formatters[ $format ], $value ); - } - - // Fallback to default behavior if format not registered - if ( is_array( $value ) || is_object( $value ) ) { - return var_export( $value, true ); - } - - // @phpstan-ignore cast.string - return (string) $value; - } - - /** - * Register built-in format handlers. - * - * This method registers the default format handlers (table, json, csv, yaml, count, ids) - * using the add_format() API, allowing them to be overridden like custom formats. - */ - public static function register_builtin_formats() { - // Register 'table' format - self::add_format( - 'table', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $args required for API consistency - static function ( $items, $fields, $formatter = null, $args = [] ) { - $ascii_pre_colorized = $args['ascii_pre_colorized'] ?? false; - if ( $formatter instanceof Formatter ) { - $formatter->show_table( $items, $fields, $ascii_pre_colorized ); - } else { - // Fallback if no formatter instance provided - $table = new Table(); - $table->setHeaders( $fields ); - foreach ( $items as $item ) { - $table->addRow( array_values( (array) $item ) ); - } - foreach ( $table->getDisplayLines() as $line ) { - WP_CLI::line( $line ); - } - } - } - ); - - // Register 'json' format - self::add_format( - 'json', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $formatter required for API consistency - static function ( $items, $fields, $formatter = null, $args = [] ) { - // For single-item display, output the item directly without array wrapper - if ( ! empty( $args['single_item'] ) && count( $items ) === 1 ) { - $item = reset( $items ); - if ( defined( 'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) { - // phpcs:ignore PHPCompatibility.Constants.NewConstants.json_partial_output_on_errorFound - echo json_encode( $item, JSON_PARTIAL_OUTPUT_ON_ERROR ); - } else { - echo json_encode( $item ); - } - } elseif ( defined( 'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) { - // phpcs:ignore PHPCompatibility.Constants.NewConstants.json_partial_output_on_errorFound - echo json_encode( $items, JSON_PARTIAL_OUTPUT_ON_ERROR ); - } else { - echo json_encode( $items ); - } - } - ); - - // Register 'csv' format - self::add_format( - 'csv', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $formatter, $args required for API consistency - static function ( $items, $fields, $formatter = null, $args = [] ) { - Utils\write_csv( STDOUT, $items, $fields ); - } - ); - - // Register 'yaml' format - self::add_format( - 'yaml', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $formatter required for API consistency - static function ( $items, $fields, $formatter = null, $args = [] ) { - // For single-item display, output the item directly without array wrapper - if ( ! empty( $args['single_item'] ) && count( $items ) === 1 ) { - $item = reset( $items ); - echo Spyc::YAMLDump( $item, 2, 0 ); - } else { - echo Spyc::YAMLDump( $items, 2, 0 ); - } - } - ); - - // Register 'count' format - self::add_format( - 'count', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $fields, $formatter, $args required for API consistency - static function ( $items, $fields, $formatter = null, $args = [] ) { - echo count( $items ); - }, - [ 'single_item' => false ] - ); - - // Register 'ids' format - self::add_format( - 'ids', - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $fields, $formatter, $args required for API consistency - static function ( $items, $fields, $formatter = null, $args = [] ) { - echo implode( ' ', $items ); - }, - [ 'single_item' => false ] - ); - - // Register single-value formats for WP_CLI::print_value() - - // Register 'json' single-value format - self::add_single_value_format( - 'json', - static function ( $value ) { - return json_encode( $value ); - } - ); - - // Register 'yaml' single-value format - self::add_single_value_format( - 'yaml', - static function ( $value ) { - /** - * @var array $value - */ - return Spyc::YAMLDump( $value, 2, 0 ); - } - ); - - $var_export_handler = static function ( $value ) { - if ( is_array( $value ) || is_object( $value ) ) { - return var_export( $value, true ); - } - return (string) $value; - }; - - // Register 'var_export' single-value format (default for arrays/objects) - self::add_single_value_format( 'var_export', $var_export_handler ); - - // Register 'plaintext' single-value format - self::add_single_value_format( 'plaintext', $var_export_handler ); - } - - /** - * Get list of all available format names. - * - * Returns built-in formats plus any custom formats that have been registered. - * The list can be filtered via the 'formatter_available_formats' hook. - * - * ## EXAMPLE - * - * // Get all available formats - * $formats = WP_CLI\Formatter::get_available_formats(); - * // Returns: [ 'table', 'json', 'csv', 'yaml', 'count', 'ids', ... custom formats ] - * - * // Filter to add a format to the list - * WP_CLI::add_hook( 'formatter_available_formats', function( $formats ) { - * $formats[] = 'my_custom_format'; - * return $formats; - * }); - * - * @return string[] Array of format names. - */ - public static function get_available_formats() { - $all_formats = array_keys( self::$custom_formatters ); - - /** - * Filter the list of available output formats. - * - * @param string[] $formats Array of format names. - */ - // @phpstan-ignore-next-line - We trust the hook to return the correct type - return WP_CLI::do_hook( 'formatter_available_formats', $all_formats ); - } - /** * Magic getter for arguments. * @@ -364,33 +61,29 @@ public function __get( $key ) { /** * Display multiple items according to the output arguments. * - * @param iterable $items The items to display. + * @param array $items * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `format()` if items in the table are pre-colorized. Default false. */ public function display_items( $items, $ascii_pre_colorized = false ) { if ( $this->args['field'] ) { $this->show_single_field( $items, $this->args['field'] ); } else { - // Convert iterator to array early to avoid consumption issues and enable validation - if ( $items instanceof Iterator ) { - $items = iterator_to_array( $items ); - } - - // Check if this is a custom formatter or a built-in format that needs field validation - // Skip validation for count/ids formats as they don't use fields - $skip_field_validation = in_array( $this->args['format'], [ 'count', 'ids' ], true ); - $is_custom_format = isset( self::$custom_formatters[ $this->args['format'] ] ); - $needs_field_validation = ! $skip_field_validation && ( in_array( $this->args['format'], [ 'csv', 'json', 'table', 'yaml' ], true ) || $is_custom_format ); - - if ( $needs_field_validation ) { - // Validate fields exist in at least one item and resolve field names with prefix support - if ( ! empty( $this->args['fields'] ) ) { - $this->validate_fields( $items ); + if ( in_array( $this->args['format'], array( 'csv', 'json', 'table' ) ) ) { + $item = is_array( $items ) && ! empty( $items ) ? array_shift( $items ) : false; + if ( $item && ! empty( $this->args['fields'] ) ) { + foreach ( $this->args['fields'] as &$field ) { + $field = $this->find_item_key( $item, $field ); + } + array_unshift( $items, $item ); } } - if ( in_array( $this->args['format'], [ 'table', 'csv' ], true ) ) { - $items = array_map( [ $this, 'transform_item_values_to_json' ], (array) $items ); + if ( in_array( $this->args['format'], array( 'table', 'csv' ) ) ) { + if ( is_object( $items ) && is_a( $items, 'Iterator' ) ) { + $items = \WP_CLI\Utils\iterator_map( $items, array( $this, 'transform_item_values_to_json' ) ); + } else { + $items = array_map( array( $this, 'transform_item_values_to_json' ), $items ); + } } $this->format( $items, $ascii_pre_colorized ); @@ -406,26 +99,18 @@ public function display_items( $items, $ascii_pre_colorized = false ) { public function display_item( $item, $ascii_pre_colorized = false ) { if ( isset( $this->args['field'] ) ) { $item = (object) $item; - $key = $this->find_item_key( $item, $this->args['field'], true ); - if ( null === $key ) { - WP_CLI::warning( "Field not found in item: {$this->args['field']}." ); - $value = null; - } else { - $value = $item->$key; - } - if ( in_array( $this->args['format'], [ 'table', 'csv' ], true ) && ( is_object( $value ) || is_array( $value ) ) ) { + $key = $this->find_item_key( $item, $this->args['field'] ); + $value = $item->$key; + if ( in_array( $this->args['format'], array( 'table', 'csv' ) ) && ( is_object( $value ) || is_array( $value ) ) ) { $value = json_encode( $value ); } - WP_CLI::print_value( + \WP_CLI::print_value( $value, - [ + array( 'format' => $this->args['format'], - ] + ) ); } else { - /** - * @var array $item - */ $this->show_multiple_fields( $item, $this->args['format'], $ascii_pre_colorized ); } } @@ -433,183 +118,110 @@ public function display_item( $item, $ascii_pre_colorized = false ) { /** * Format items according to arguments. * - * @param iterable $items Items. + * @param array $items * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if items in the table are pre-colorized. Default false. */ - private function format( $items, $ascii_pre_colorized = false ): void { + private function format( $items, $ascii_pre_colorized = false ) { $fields = $this->args['fields']; - // Convert iterator to array if needed - if ( ! is_array( $items ) ) { - $items = iterator_to_array( $items ); - } + switch ( $this->args['format'] ) { + case 'count': + if ( ! is_array( $items ) ) { + $items = iterator_to_array( $items ); + } + echo count( $items ); + break; - // Check if a formatter is registered for this format - if ( isset( self::$custom_formatters[ $this->args['format'] ] ) ) { - // Special handling for 'ids' and 'count' formats - they work with raw items - if ( in_array( $this->args['format'], [ 'ids', 'count' ], true ) ) { - call_user_func( self::$custom_formatters[ $this->args['format'] ], $items, $fields, $this, [] ); - return; - } + case 'ids': + if ( ! is_array( $items ) ) { + $items = iterator_to_array( $items ); + } + echo implode( ' ', $items ); + break; - // Filter columns exactly once - $formatted_items = []; - foreach ( $items as $item ) { - if ( is_array( $item ) || is_object( $item ) ) { - // @phpstan-ignore-next-line - $item is guaranteed to be array|object here - $formatted_items[] = Utils\pick_fields( $item, $fields ); - } else { - WP_CLI::debug( 'Skipping item that is neither array nor object in format handler.', 'formatter' ); + case 'table': + self::show_table( $items, $fields, $ascii_pre_colorized ); + break; + + case 'csv': + \WP_CLI\Utils\write_csv( STDOUT, $items, $fields ); + break; + + case 'json': + case 'yaml': + $out = array(); + foreach ( $items as $item ) { + $out[] = \WP_CLI\Utils\pick_fields( $item, $fields ); } - } - // Truncate cell values exactly once for table/CSV output - if ( in_array( $this->args['format'], [ 'table', 'csv' ], true ) ) { - foreach ( $formatted_items as &$row ) { - foreach ( $row as $key => $value ) { - if ( is_string( $value ) && strlen( $value ) > self::MAX_CELL_WIDTH ) { - $row[ $key ] = substr( $value, 0, self::MAX_CELL_WIDTH ) . '...'; - } + if ( 'json' === $this->args['format'] ) { + if ( defined( 'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) { + echo json_encode( $out, JSON_PARTIAL_OUTPUT_ON_ERROR ); + } else { + echo json_encode( $out ); } + } elseif ( 'yaml' === $this->args['format'] ) { + echo Spyc::YAMLDump( $out, 2, 0 ); } - unset( $row ); - } + break; - $args = [ 'ascii_pre_colorized' => $ascii_pre_colorized ]; - $handler = self::$custom_formatters[ $this->args['format'] ]; - call_user_func( $handler, $formatted_items, $fields, $this, $args ); - return; + default: + \WP_CLI::error( 'Invalid format: ' . $this->args['format'] ); } - - // If no formatter is registered, show error - WP_CLI::error( 'Invalid format: ' . $this->args['format'] ); } /** * Show a single field from a list of items. * - * @param iterable $items Array of objects to show fields from - * @param string $field The field to show + * @param array Array of objects to show fields from + * @param string The field to show */ - private function show_single_field( $items, $field ): void { - $key = null; - $values = []; - $field_found = false; - $item_count = 0; + private function show_single_field( $items, $field ) { + $key = null; + $values = array(); foreach ( $items as $item ) { - ++$item_count; $item = (object) $item; - // Resolve the key on first item that has the field - if ( ! $field_found && null === $key ) { - $key = $this->find_item_key( $item, $field, true ); - if ( null !== $key ) { - $field_found = true; - } + if ( null === $key ) { + $key = $this->find_item_key( $item, $field ); } - // Get value if key exists - $value = ( null !== $key && isset( $item->$key ) ) ? $item->$key : null; - - if ( 'json' === $this->args['format'] ) { - $values[] = $value; + if ( 'json' == $this->args['format'] ) { + $values[] = $item->$key; } else { - WP_CLI::print_value( - $value, - [ + \WP_CLI::print_value( + $item->$key, + array( 'format' => $this->args['format'], - ] + ) ); } } - if ( ! $field_found && $item_count > 0 ) { - WP_CLI::warning( "Field not found in any item: $field." ); - } - - if ( 'json' === $this->args['format'] ) { + if ( 'json' == $this->args['format'] ) { echo json_encode( $values ); } } - /** - * Validate that requested fields exist in at least one item. - * Warns if a field doesn't exist in any item. - * Also resolves field names to their actual keys (including prefixes). - * - * @param iterable $items Items to validate - */ - private function validate_fields( $items ): void { - // Track which fields have been found and their resolved keys - $fields_to_find = array_flip( $this->args['fields'] ); - $resolved_fields = []; - $fields_count = count( $fields_to_find ); - $found_count = 0; - $item_count = 0; - - // Iterate through items once and check all fields - foreach ( $items as $item ) { - ++$item_count; - // Check each field that hasn't been found yet - foreach ( $fields_to_find as $field => $_ ) { - $key = $this->find_item_key( $item, $field, true ); - if ( null !== $key ) { - // Store the resolved field name - $resolved_fields[ $field ] = $key; - // Mark this field as found - unset( $fields_to_find[ $field ] ); - ++$found_count; - // If all fields found, we can stop early - if ( $found_count === $fields_count ) { - break 2; - } - } - } - } - - // Update the fields array with resolved field names - foreach ( $this->args['fields'] as &$field ) { - if ( isset( $resolved_fields[ $field ] ) ) { - $field = $resolved_fields[ $field ]; - } - } - unset( $field ); // Break the reference to avoid issues with subsequent foreach loops - - // Only warn about missing fields if there were items to check - if ( $item_count > 0 ) { - // Warn about any fields that weren't found in any item - foreach ( $fields_to_find as $missing_field => $_ ) { - WP_CLI::warning( "Field not found in any item: $missing_field." ); - } - } - } - /** * Find an object's key. * If $prefix is set, a key with that prefix will be prioritized. * - * @param array|object $item - * @param string $field - * @param bool $lenient If true, return null instead of erroring when field is not found. - * @return string|null + * @param object $item + * @param string $field + * @return string $key */ - private function find_item_key( $item, $field, $lenient = false ) { - foreach ( [ $field, $this->prefix . '_' . $field ] as $maybe_key ) { - if ( - ( is_object( $item ) && ( property_exists( $item, $maybe_key ) || isset( $item->$maybe_key ) ) ) || - ( is_array( $item ) && array_key_exists( $maybe_key, $item ) ) - ) { + private function find_item_key( $item, $field ) { + foreach ( array( $field, $this->prefix . '_' . $field ) as $maybe_key ) { + if ( ( is_object( $item ) && ( property_exists( $item, $maybe_key ) || isset( $item->$maybe_key ) ) ) || ( is_array( $item ) && array_key_exists( $maybe_key, $item ) ) ) { $key = $maybe_key; break; } } if ( ! isset( $key ) ) { - if ( $lenient ) { - return null; - } - WP_CLI::error( "Invalid field: $field." ); + \WP_CLI::error( "Invalid field: $field." ); } return $key; @@ -618,25 +230,19 @@ private function find_item_key( $item, $field, $lenient = false ) { /** * Show multiple fields of an object. * - * @param iterable $data Data to display - * @param string $format Format to display the data in - * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if the item in the table is pre-colorized. Default false. + * @param object|array $data Data to display + * @param string $format Format to display the data in + * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if the item in the table is pre-colorized. Default false. */ - private function show_multiple_fields( $data, $format, $ascii_pre_colorized = false ): void { + private function show_multiple_fields( $data, $format, $ascii_pre_colorized = false ) { - $true_fields = []; + $true_fields = array(); foreach ( $this->args['fields'] as $field ) { - $key = $this->find_item_key( $data, $field, true ); - if ( null === $key ) { - // Field doesn't exist, show warning - WP_CLI::warning( "Field not found in item: $field." ); - } else { - $true_fields[] = $key; - } + $true_fields[] = $this->find_item_key( $data, $field ); } foreach ( $data as $key => $value ) { - if ( ! in_array( $key, $true_fields, true ) ) { + if ( ! in_array( $key, $true_fields ) ) { if ( is_array( $data ) ) { unset( $data[ $key ] ); } elseif ( is_object( $data ) ) { @@ -645,82 +251,76 @@ private function show_multiple_fields( $data, $format, $ascii_pre_colorized = fa } } - $ordered_data = []; + switch ( $format ) { - foreach ( $true_fields as $field ) { - $ordered_data[ $field ] = ( ( (array) $data )[ $field ] ); - } + case 'table': + case 'csv': + $rows = $this->assoc_array_to_rows( $data ); + $fields = array( 'Field', 'Value' ); + if ( 'table' == $format ) { + self::show_table( $rows, $fields, $ascii_pre_colorized ); + } elseif ( 'csv' == $format ) { + \WP_CLI\Utils\write_csv( STDOUT, $rows, $fields ); + } + break; - // Check if a formatter is registered for this format - if ( isset( self::$custom_formatters[ $format ] ) ) { - // Verify the format supports single-item display - $options = self::$format_options[ $format ] ?? []; - if ( isset( $options['single_item'] ) && ! $options['single_item'] ) { - WP_CLI::error( 'Invalid format: ' . $format ); - } + case 'yaml': + case 'json': + \WP_CLI::print_value( + $data, + array( + 'format' => $format, + ) + ); + break; + + default: + \WP_CLI::error( 'Invalid format: ' . $format ); + break; - // For table and csv formats in single-item display, convert to rows format - if ( in_array( $format, [ 'table', 'csv' ], true ) ) { - $rows = $this->assoc_array_to_rows( $ordered_data ); - $fields = [ 'Field', 'Value' ]; - $args = [ - 'single_item' => true, - 'ascii_pre_colorized' => $ascii_pre_colorized, - ]; - call_user_func( self::$custom_formatters[ $format ], $rows, $fields, $this, $args ); - } else { - $args = [ 'single_item' => true ]; - call_user_func( self::$custom_formatters[ $format ], [ $ordered_data ], array_keys( $ordered_data ), $this, $args ); - } - return; } - // If no formatter is registered, show error - WP_CLI::error( 'Invalid format: ' . $format ); } /** * Show items in a \cli\Table. * - * @param iterable $items Items. - * @param array $fields Fields. + * @param array $items + * @param array $fields * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `Table::setAsciiPreColorized()` if items in the table are pre-colorized. Default false. */ - private function show_table( $items, $fields, $ascii_pre_colorized = false ) { - $table = new Table(); + private static function show_table( $items, $fields, $ascii_pre_colorized = false ) { + $table = new \cli\Table(); - $enabled = WP_CLI::get_runner()->in_color(); + $enabled = \cli\Colors::shouldColorize(); if ( $enabled ) { - Colors::disable( true ); + \cli\Colors::disable( true ); } $table->setAsciiPreColorized( $ascii_pre_colorized ); $table->setHeaders( $fields ); - $table->setAlignments( - $this->args['alignments'] - ); foreach ( $items as $item ) { - $table->addRow( array_values( (array) $item ) ); + $table->addRow( array_values( \WP_CLI\Utils\pick_fields( $item, $fields ) ) ); } foreach ( $table->getDisplayLines() as $line ) { - WP_CLI::line( $line ); + \WP_CLI::line( $line ); } if ( $enabled ) { - Colors::enable( true ); + \cli\Colors::enable( true ); } } /** * Format an associative array as a table. * - * @param iterable $fields Fields and values to format - * @return array + * @param array $fields Fields and values to format + * @return array $rows */ private function assoc_array_to_rows( $fields ) { - $rows = []; + $rows = array(); foreach ( $fields as $field => $value ) { @@ -728,37 +328,24 @@ private function assoc_array_to_rows( $fields ) { $value = json_encode( $value ); } - // Truncate large values for table/CSV output performance - if ( is_string( $value ) && strlen( $value ) > self::MAX_CELL_WIDTH ) { - $value = substr( $value, 0, self::MAX_CELL_WIDTH ) . '...'; - } - - $rows[] = (object) [ + $rows[] = (object) array( 'Field' => $field, 'Value' => $value, - ]; + ); } return $rows; } /** - * Transforms item values for string-based output formats (table/CSV). - * - * Converts complex types to strings: - * - Objects and arrays are converted to JSON strings - * - Booleans are converted to "true" or "false" + * Transforms objects and arrays to JSON as necessary * - * @param array|object $item + * @param mixed $item * @return mixed */ public function transform_item_values_to_json( $item ) { foreach ( $this->args['fields'] as $field ) { - $true_field = $this->find_item_key( $item, $field, true ); - if ( null === $true_field ) { - // Field doesn't exist in this item, skip it - continue; - } + $true_field = $this->find_item_key( $item, $field ); $value = is_object( $item ) ? $item->$true_field : $item[ $true_field ]; if ( is_array( $value ) || is_object( $value ) ) { if ( is_object( $item ) ) { @@ -766,15 +353,9 @@ public function transform_item_values_to_json( $item ) { } elseif ( is_array( $item ) ) { $item[ $true_field ] = json_encode( $value ); } - } elseif ( is_bool( $value ) ) { - // Convert boolean to string representation for table/CSV display - if ( is_object( $item ) ) { - $item->$true_field = $value ? 'true' : 'false'; - } elseif ( is_array( $item ) ) { - $item[ $true_field ] = $value ? 'true' : 'false'; - } } } return $item; } + } diff --git a/php/WP_CLI/Inflector.php b/php/WP_CLI/Inflector.php deleted file mode 100644 index 28989943d..000000000 --- a/php/WP_CLI/Inflector.php +++ /dev/null @@ -1,555 +0,0 @@ -. - */ - -namespace WP_CLI; - -/** - * Doctrine inflector has static methods for inflecting text. - * - * The methods in these classes are from several different sources collected - * across several different php projects and several different authors. The - * original author names and emails are not known. - * - * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications. - * - * @link www.doctrine-project.org - * @since 1.0 - * @author Konsta Vesterinen - * @author Jonathan H. Wage - */ -class Inflector { - - /** - * Plural inflector rules. - * - * @var array - */ - private static $plural = [ - 'rules' => [ - '/(s)tatus$/i' => '\1\2tatuses', - '/(quiz)$/i' => '\1zes', - '/^(ox)$/i' => '\1\2en', - '/([m|l])ouse$/i' => '\1ice', - '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', - '/(x|ch|ss|sh)$/i' => '\1es', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(hive)$/i' => '\1s', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/sis$/i' => 'ses', - '/([ti])um$/i' => '\1a', - '/(p)erson$/i' => '\1eople', - '/(m)an$/i' => '\1en', - '/(c)hild$/i' => '\1hildren', - '/(f)oot$/i' => '\1eet', - '/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', - '/us$/i' => 'uses', - '/(alias)$/i' => '\1es', - '/(analys|ax|cris|test|thes)is$/i' => '\1es', - '/s$/' => 's', - '/^$/' => '', - '/$/' => 's', - ], - 'uninflected' => [ - '.*[nrlm]ese', - '.*deer', - '.*fish', - '.*measles', - '.*ois', - '.*pox', - '.*sheep', - 'people', - 'cookie', - ], - 'irregular' => [ - 'atlas' => 'atlases', - 'axe' => 'axes', - 'beef' => 'beefs', - 'brother' => 'brothers', - 'cafe' => 'cafes', - 'chateau' => 'chateaux', - 'child' => 'children', - 'cookie' => 'cookies', - 'corpus' => 'corpuses', - 'cow' => 'cows', - 'criterion' => 'criteria', - 'curriculum' => 'curricula', - 'demo' => 'demos', - 'domino' => 'dominoes', - 'echo' => 'echoes', - 'foot' => 'feet', - 'fungus' => 'fungi', - 'ganglion' => 'ganglions', - 'genie' => 'genies', - 'genus' => 'genera', - 'graffito' => 'graffiti', - 'hippopotamus' => 'hippopotami', - 'hoof' => 'hoofs', - 'human' => 'humans', - 'iris' => 'irises', - 'leaf' => 'leaves', - 'loaf' => 'loaves', - 'man' => 'men', - 'medium' => 'media', - 'memorandum' => 'memoranda', - 'money' => 'monies', - 'mongoose' => 'mongooses', - 'motto' => 'mottoes', - 'move' => 'moves', - 'mythos' => 'mythoi', - 'niche' => 'niches', - 'nucleus' => 'nuclei', - 'numen' => 'numina', - 'occiput' => 'occiputs', - 'octopus' => 'octopuses', - 'opus' => 'opuses', - 'ox' => 'oxen', - 'penis' => 'penises', - 'person' => 'people', - 'plateau' => 'plateaux', - 'runner-up' => 'runners-up', - 'sex' => 'sexes', - 'soliloquy' => 'soliloquies', - 'son-in-law' => 'sons-in-law', - 'syllabus' => 'syllabi', - 'testis' => 'testes', - 'thief' => 'thieves', - 'tooth' => 'teeth', - 'tornado' => 'tornadoes', - 'trilby' => 'trilbys', - 'turf' => 'turfs', - 'volcano' => 'volcanoes', - ], - ]; - - /** - * Singular inflector rules. - * - * @var array - */ - private static $singular = [ - 'rules' => [ - '/(s)tatuses$/i' => '\1\2tatus', - '/^(.*)(menu)s$/i' => '\1\2', - '/(quiz)zes$/i' => '\\1', - '/(matr)ices$/i' => '\1ix', - '/(vert|ind)ices$/i' => '\1ex', - '/^(ox)en/i' => '\1', - '/(alias)(es)*$/i' => '\1', - '/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', - '/([ftw]ax)es/i' => '\1', - '/(analys|ax|cris|test|thes)es$/i' => '\1is', - '/(shoe|slave)s$/i' => '\1', - '/(o)es$/i' => '\1', - '/ouses$/' => 'ouse', - '/([^a])uses$/' => '\1us', - '/([m|l])ice$/i' => '\1ouse', - '/(x|ch|ss|sh)es$/i' => '\1', - '/(m)ovies$/i' => '\1\2ovie', - '/(s)eries$/i' => '\1\2eries', - '/([^aeiouy]|qu)ies$/i' => '\1y', - '/([lr])ves$/i' => '\1f', - '/(tive)s$/i' => '\1', - '/(hive)s$/i' => '\1', - '/(drive)s$/i' => '\1', - '/([^fo])ves$/i' => '\1fe', - '/(^analy)ses$/i' => '\1sis', - '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', - '/([ti])a$/i' => '\1um', - '/(p)eople$/i' => '\1\2erson', - '/(m)en$/i' => '\1an', - '/(c)hildren$/i' => '\1\2hild', - '/(f)eet$/i' => '\1oot', - '/(n)ews$/i' => '\1\2ews', - '/eaus$/' => 'eau', - '/^(.*us)$/' => '\\1', - '/s$/i' => '', - ], - 'uninflected' => [ - '.*[nrlm]ese', - '.*deer', - '.*fish', - '.*measles', - '.*ois', - '.*pox', - '.*sheep', - '.*ss', - ], - 'irregular' => [ - 'criteria' => 'criterion', - 'curves' => 'curve', - 'emphases' => 'emphasis', - 'foes' => 'foe', - 'hoaxes' => 'hoax', - 'media' => 'medium', - 'neuroses' => 'neurosis', - 'waves' => 'wave', - 'oases' => 'oasis', - ], - ]; - - /** - * Words that should not be inflected. - * - * @var array - */ - private static $uninflected = [ - 'Amoyese', - 'bison', - 'Borghese', - 'bream', - 'breeches', - 'britches', - 'buffalo', - 'cantus', - 'carp', - 'chassis', - 'clippers', - 'cod', - 'coitus', - 'Congoese', - 'contretemps', - 'corps', - 'debris', - 'diabetes', - 'djinn', - 'eland', - 'elk', - 'equipment', - 'Faroese', - 'flounder', - 'Foochowese', - 'gallows', - 'Genevese', - 'Genoese', - 'Gilbertese', - 'graffiti', - 'headquarters', - 'herpes', - 'hijinks', - 'Hottentotese', - 'information', - 'innings', - 'jackanapes', - 'Kiplingese', - 'Kongoese', - 'Lucchese', - 'mackerel', - 'Maltese', - '.*?media', - 'mews', - 'moose', - 'mumps', - 'Nankingese', - 'news', - 'nexus', - 'Niasese', - 'Pekingese', - 'Piedmontese', - 'pincers', - 'Pistoiese', - 'pliers', - 'Portuguese', - 'proceedings', - 'rabies', - 'rice', - 'rhinoceros', - 'salmon', - 'Sarawakese', - 'scissors', - 'sea[- ]bass', - 'series', - 'Shavese', - 'shears', - 'siemens', - 'species', - 'staff', - 'swine', - 'testes', - 'trousers', - 'trout', - 'tuna', - 'Vermontese', - 'Wenchowese', - 'whiting', - 'wildebeest', - 'Yengeese', - ]; - - /** - * Method cache array. - * - * @var array - */ - private static $cache = []; - - /** - * The initial state of Inflector so reset() works. - * - * @var array - */ - private static $initial_state = []; - - /** - * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. - * - * @param string $word The word to tableize. - * - * @return string The tableized word. - */ - public static function tableize( $word ) { - return strtolower( (string) preg_replace( '~(?<=\\w)([A-Z])~', '_$1', $word ) ); - } - - /** - * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. - * - * @param string $word The word to classify. - * - * @return string The classified word. - */ - public static function classify( $word ) { - return str_replace( ' ', '', ucwords( strtr( $word, '_-', ' ' ) ) ); - } - - /** - * Camelizes a word. This uses the classify() method and turns the first character to lowercase. - * - * @param string $word The word to camelize. - * - * @return string The camelized word. - */ - public static function camelize( $word ) { - return lcfirst( self::classify( $word ) ); - } - - /** - * Uppercases words with configurable delimiters between words. - * - * Takes a string and capitalizes all of the words, like PHP's built-in - * ucwords function. This extends that behavior, however, by allowing the - * word delimiters to be configured, rather than only separating on - * whitespace. - * - * Here is an example: - * - * - * - * - * @param string $string The string to operate on. - * @param string $delimiters A list of word separators. - * - * @return string The string with all delimiter-separated words capitalized. - */ - public static function ucwords( $string, $delimiters = " \n\t\r\0\x0B-" ) { - return (string) preg_replace_callback( - '/[^' . preg_quote( $delimiters, '/' ) . ']+/', - function ( $matches ) { - return ucfirst( $matches[0] ); - }, - $string - ); - } - - /** - * Clears Inflectors inflected value caches, and resets the inflection - * rules to the initial values. - * - * @return void - */ - public static function reset() { - if ( empty( self::$initial_state ) ) { - self::$initial_state = get_class_vars( 'Inflector' ); - - return; - } - - foreach ( self::$initial_state as $key => $val ) { - if ( 'initial_state' !== $key ) { - self::${$key} = $val; - } - } - } - - /** - * Adds custom inflection $rules, of either 'plural' or 'singular' $type. - * - * ### Usage: - * - * {{{ - * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); - * Inflector::rules('plural', array( - * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), - * 'uninflected' => array('dontinflectme'), - * 'irregular' => array('red' => 'redlings') - * )); - * }}} - * - * @param string $type The type of inflection, either 'plural' or 'singular' - * @param array $rules An array of rules to be added. - * @param boolean $reset If true, will unset default inflections for all - * new rules that are being defined in $rules. - * - * @return void - */ - public static function rules( $type, $rules, $reset = false ) { - foreach ( $rules as $rule => $pattern ) { - if ( ! is_array( $pattern ) ) { - continue; - } - - if ( $reset ) { - self::${$type}[ $rule ] = $pattern; - } else { - self::${$type}[ $rule ] = ( 'uninflected' === $rule ) - ? array_merge( $pattern, self::${$type}[ $rule ] ) - : $pattern + self::${$type}[ $rule ]; - } - - unset( $rules[ $rule ], self::${$type}[ 'cache' . ucfirst( $rule ) ] ); - - if ( isset( self::${$type}['merged'][ $rule ] ) ) { - unset( self::${$type}['merged'][ $rule ] ); - } - - if ( 'plural' === $type ) { - self::$cache['pluralize'] = []; - self::$cache['tableize'] = []; - } elseif ( 'singular' === $type ) { - self::$cache['singularize'] = []; - } - } - - self::${$type}['rules'] = $rules + self::${$type}['rules']; - } - - /** - * Returns a word in plural form. - * - * @param string $word The word in singular form. - * - * @return string The word in plural form. - */ - public static function pluralize( $word ) { - if ( isset( self::$cache['pluralize'][ $word ] ) ) { - return self::$cache['pluralize'][ $word ]; - } - - if ( ! isset( self::$plural['merged']['irregular'] ) ) { - self::$plural['merged']['irregular'] = self::$plural['irregular']; - } - - if ( ! isset( self::$plural['merged']['uninflected'] ) ) { - self::$plural['merged']['uninflected'] = array_merge( self::$plural['uninflected'], self::$uninflected ); - } - - if ( ! isset( self::$plural['cacheUninflected'] ) || ! isset( self::$plural['cacheIrregular'] ) ) { - self::$plural['cacheUninflected'] = '(?:' . implode( '|', self::$plural['merged']['uninflected'] ) . ')'; - self::$plural['cacheIrregular'] = '(?:' . implode( '|', array_keys( self::$plural['merged']['irregular'] ) ) . ')'; - } - - if ( preg_match( '/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs ) ) { - self::$cache['pluralize'][ $word ] = $regs[1] . substr( $word, 0, 1 ) . substr( self::$plural['merged']['irregular'][ strtolower( $regs[2] ) ], 1 ); - - return self::$cache['pluralize'][ $word ]; - } - - if ( preg_match( '/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs ) ) { - self::$cache['pluralize'][ $word ] = $word; - - return $word; - } - - foreach ( self::$plural['rules'] as $rule => $replacement ) { - if ( preg_match( $rule, $word ) ) { - self::$cache['pluralize'][ $word ] = (string) preg_replace( $rule, $replacement, $word ); - - return self::$cache['pluralize'][ $word ]; - } - } - - // Just so a string is always returned. - // This should never be reached. - return $word; - } - - /** - * Returns a word in singular form. - * - * @param string $word The word in plural form. - * - * @return string The word in singular form. - */ - public static function singularize( $word ) { - if ( isset( self::$cache['singularize'][ $word ] ) ) { - return self::$cache['singularize'][ $word ]; - } - - if ( ! isset( self::$singular['merged']['uninflected'] ) ) { - self::$singular['merged']['uninflected'] = array_merge( - self::$singular['uninflected'], - self::$uninflected - ); - } - - if ( ! isset( self::$singular['merged']['irregular'] ) ) { - self::$singular['merged']['irregular'] = array_merge( - self::$singular['irregular'], - array_flip( self::$plural['irregular'] ) - ); - } - - if ( ! isset( self::$singular['cacheUninflected'] ) || ! isset( self::$singular['cacheIrregular'] ) ) { - self::$singular['cacheUninflected'] = '(?:' . join( '|', self::$singular['merged']['uninflected'] ) . ')'; - self::$singular['cacheIrregular'] = '(?:' . join( '|', array_keys( self::$singular['merged']['irregular'] ) ) . ')'; - } - - if ( preg_match( '/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs ) ) { - self::$cache['singularize'][ $word ] = $regs[1] . substr( $word, 0, 1 ) . substr( self::$singular['merged']['irregular'][ strtolower( $regs[2] ) ], 1 ); - - return self::$cache['singularize'][ $word ]; - } - - if ( preg_match( '/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs ) ) { - self::$cache['singularize'][ $word ] = $word; - - return $word; - } - - foreach ( self::$singular['rules'] as $rule => $replacement ) { - if ( preg_match( $rule, $word ) ) { - self::$cache['singularize'][ $word ] = (string) preg_replace( $rule, $replacement, $word ); - - return self::$cache['singularize'][ $word ]; - } - } - - self::$cache['singularize'][ $word ] = $word; - - return $word; - } -} diff --git a/php/WP_CLI/Iterators/CSV.php b/php/WP_CLI/Iterators/CSV.php index 5a23f4aa0..c90f079f0 100644 --- a/php/WP_CLI/Iterators/CSV.php +++ b/php/WP_CLI/Iterators/CSV.php @@ -2,113 +2,60 @@ namespace WP_CLI\Iterators; -use Countable; -use Iterator; -use ReturnTypeWillChange; -use SplFileObject; -use WP_CLI; - /** * Allows incrementally reading and parsing lines from a CSV file. - * - * @implements \Iterator */ -class CSV implements Countable, Iterator { +class CSV implements \Iterator { const ROW_SIZE = 4096; - /** - * The name of the CSV file. - * - * @var string - */ - private $filename; - - /** - * The file pointer resource. - * - * @var resource - */ - private $file_pointer; - - /** - * The CSV delimiter. - * - * @var string - */ - private $delimiter; + private $filePointer; - /** - * The column names. - * - * @var array - */ + private $delimiter; private $columns; - /** - * The current index in the iterator. - * - * @var int - */ - private $current_index; - - /** - * The current element (row) or false. - * - * @var string[]|false - */ - private $current_element; - - /** - * Instantiate a new CSV iterator. - * - * @param string $filename The name of the CSV file. - * @param string $delimiter The CSV delimiter. - */ - public function __construct( $filename, $delimiter = ',' ) { - $this->filename = $filename; - $file_pointer = fopen( $filename, 'rb' ); + private $currentIndex; + private $currentElement; - if ( ! $file_pointer ) { - WP_CLI::error( sprintf( 'Could not open file: %s', $filename ) ); + public function __construct( $filename, $delimiter = ',' ) { + $this->filePointer = fopen( $filename, 'rb' ); + if ( ! $this->filePointer ) { + \WP_CLI::error( sprintf( 'Could not open file: %s', $filename ) ); } - $this->file_pointer = $file_pointer; - $this->delimiter = $delimiter; + $this->delimiter = $delimiter; } - #[ReturnTypeWillChange] public function rewind() { - rewind( $this->file_pointer ); + rewind( $this->filePointer ); - $this->columns = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter, '"', '\\' ) ?: []; + $this->columns = fgetcsv( $this->filePointer, self::ROW_SIZE, $this->delimiter ); - $this->current_index = -1; + $this->currentIndex = -1; $this->next(); } - #[ReturnTypeWillChange] public function current() { - return $this->current_element; + return $this->currentElement; } - #[ReturnTypeWillChange] public function key() { - return $this->current_index; + return $this->currentIndex; } - #[ReturnTypeWillChange] public function next() { - $this->current_element = false; + $this->currentElement = false; while ( true ) { - $row = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter, '"', '\\' ); + $str = fgets( $this->filePointer ); - if ( false === $row ) { + if ( false === $str ) { break; } - $element = []; + $row = str_getcsv( $str, $this->delimiter ); + + $element = array(); foreach ( $this->columns as $i => $key ) { if ( isset( $row[ $i ] ) ) { $element[ $key ] = $row[ $i ]; @@ -116,26 +63,16 @@ public function next() { } if ( ! empty( $element ) ) { - $this->current_element = $element; - ++$this->current_index; + $this->currentElement = $element; + $this->currentIndex++; break; } } } - /** - * @return int<0, max> - */ - #[ReturnTypeWillChange] - public function count() { - $file = new SplFileObject( $this->filename, 'r' ); - $file->seek( PHP_INT_MAX ); - return max( 0, $file->key() + 1 ); - } - - #[ReturnTypeWillChange] public function valid() { - return is_array( $this->current_element ); + return is_array( $this->currentElement ); } } + diff --git a/php/WP_CLI/Iterators/Exception.php b/php/WP_CLI/Iterators/Exception.php index e263cf7ed..e7320e077 100644 --- a/php/WP_CLI/Iterators/Exception.php +++ b/php/WP_CLI/Iterators/Exception.php @@ -2,6 +2,5 @@ namespace WP_CLI\Iterators; -use RuntimeException; +class Exception extends \RuntimeException {} -class Exception extends RuntimeException {} diff --git a/php/WP_CLI/Iterators/Query.php b/php/WP_CLI/Iterators/Query.php index 2fbaa7447..7221d9711 100644 --- a/php/WP_CLI/Iterators/Query.php +++ b/php/WP_CLI/Iterators/Query.php @@ -2,85 +2,23 @@ namespace WP_CLI\Iterators; -use Iterator; - /** * Iterates over results of a query, split into many queries via LIMIT and OFFSET * * @source https://gist.github.com/4060005 - * - * @implements \Iterator */ -class Query implements Iterator { +class Query implements \Iterator { - /** - * How many rows to retrieve at once. - * - * @var int - */ private $chunk_size; - - /** - * The query as a string. - * - * @var string - */ private $query = ''; - - /** - * The count query as a string. - * - * @var string - */ private $count_query = ''; - /** - * The global index in the iterator. - * - * @var int - */ private $global_index = 0; - - /** - * The index in the current chunk of results. - * - * @var int - */ private $index_in_results = 0; - - /** - * The current chunk of results. - * - * @var array - */ - private $results = []; - - /** - * The total row count. - * - * @var int - */ + private $results = array(); private $row_count = 0; - - /** - * The current offset for queries. - * - * @var int - */ private $offset = 0; - - /** - * The database connection object. - * - * @var \wpdb - */ private $db = null; - - /** - * Whether the iterator is depleted. - * - * @var bool - */ private $depleted = false; /** @@ -94,23 +32,19 @@ class Query implements Iterator { * * * @param string $query The query as a string. It shouldn't include any LIMIT clauses - * @param int $chunk_size How many rows to retrieve at once; default value is 500 (optional) + * @param number $chunk_size How many rows to retrieve at once; default value is 500 (optional) */ public function __construct( $query, $chunk_size = 500 ) { - /** - * @var \wpdb $wpdb - */ - global $wpdb; $this->query = $query; - $this->count_query = (string) preg_replace( '/^.*? FROM /', 'SELECT COUNT(*) FROM ', $query, 1, $replacements ); + $this->count_query = preg_replace( '/^.*? FROM /', 'SELECT COUNT(*) FROM ', $query, 1, $replacements ); if ( 1 !== $replacements ) { $this->count_query = ''; } $this->chunk_size = $chunk_size; - $this->db = $wpdb; + $this->db = $GLOBALS['wpdb']; } /** @@ -120,12 +54,12 @@ public function __construct( $query, $chunk_size = 500 ) { * longer be returned by the original query, the offset must be reduced to * iterate over all remaining rows. */ - private function adjust_offset_for_shrinking_result_set(): void { + private function adjust_offset_for_shrinking_result_set() { if ( empty( $this->count_query ) ) { return; } - $row_count = (int) $this->db->get_var( $this->count_query ); + $row_count = $this->db->get_var( $this->count_query ); if ( $row_count < $this->row_count ) { $this->offset -= $this->row_count - $row_count; @@ -137,10 +71,10 @@ private function adjust_offset_for_shrinking_result_set(): void { private function load_items_from_db() { $this->adjust_offset_for_shrinking_result_set(); - $query = $this->query . sprintf( ' LIMIT %d OFFSET %d', $this->chunk_size, $this->offset ); - $results = $this->db->get_results( $query ); + $query = $this->query . sprintf( ' LIMIT %d OFFSET %d', $this->chunk_size, $this->offset ); + $this->results = $this->db->get_results( $query ); - if ( ! $results ) { + if ( ! $this->results ) { if ( $this->db->last_error ) { throw new Exception( 'Database error: ' . $this->db->last_error ); } @@ -148,38 +82,31 @@ private function load_items_from_db() { return false; } - $this->results = $results; - $this->offset += $this->chunk_size; return true; } - #[\ReturnTypeWillChange] public function current() { return $this->results[ $this->index_in_results ]; } - #[\ReturnTypeWillChange] public function key() { return $this->global_index; } - #[\ReturnTypeWillChange] public function next() { - ++$this->index_in_results; - ++$this->global_index; + $this->index_in_results++; + $this->global_index++; } - #[\ReturnTypeWillChange] public function rewind() { - $this->results = []; - $this->global_index = 0; + $this->results = array(); + $this->global_index = 0; $this->index_in_results = 0; - $this->offset = 0; - $this->depleted = false; + $this->offset = 0; + $this->depleted = false; } - #[\ReturnTypeWillChange] public function valid() { if ( $this->depleted ) { return false; @@ -200,3 +127,4 @@ public function valid() { return true; } } + diff --git a/php/WP_CLI/Iterators/Table.php b/php/WP_CLI/Iterators/Table.php index 42d80741f..f3ee71757 100644 --- a/php/WP_CLI/Iterators/Table.php +++ b/php/WP_CLI/Iterators/Table.php @@ -28,6 +28,7 @@ class Table extends Query { * } * * + * * @param array $args Supported arguments: * table – the name of the database table * fields – an array of columns to get from the table, '*' is a valid value and the default @@ -37,21 +38,21 @@ class Table extends Query { * it's a key/value pair. In the latter case the value is automatically quoted and escaped * append - add arbitrary extra SQL */ - public function __construct( $args = [] ) { - $defaults = [ - 'fields' => '*', - 'where' => [], - 'append' => '', - 'table' => null, + public function __construct( $args = array() ) { + $defaults = array( + 'fields' => '*', + 'where' => array(), + 'append' => '', + 'table' => null, 'chunk_size' => 500, - ]; - $table = $args['table']; - $args = array_merge( $defaults, $args ); + ); + $table = $args['table']; + $args = array_merge( $defaults, $args ); - $fields = self::build_fields( $args['fields'] ); + $fields = self::build_fields( $args['fields'] ); $conditions = self::build_where_conditions( $args['where'] ); - $where_sql = $conditions ? " WHERE $conditions" : ''; - $query = "SELECT $fields FROM `$table` $where_sql {$args['append']}"; + $where_sql = $conditions ? " WHERE $conditions" : ''; + $query = "SELECT $fields FROM `$table` $where_sql {$args['append']}"; parent::__construct( $query, $args['chunk_size'] ); } @@ -75,24 +76,19 @@ function ( $v ) { private static function build_where_conditions( $where ) { global $wpdb; if ( is_array( $where ) ) { - $conditions = []; + $conditions = array(); foreach ( $where as $key => $value ) { if ( is_array( $value ) ) { - /** - * @var array $value - * @var string $values - */ - $values = esc_sql( implode( ',', $value ) ); - $conditions[] = $key . ' IN (' . $values . ')'; + $conditions[] = $key . ' IN (' . esc_sql( implode( ',', $value ) ) . ')'; } elseif ( is_numeric( $key ) ) { $conditions[] = $value; } else { $conditions[] = $key . $wpdb->prepare( ' = %s', $value ); } } - /** @var array $conditions */ $where = implode( ' AND ', $conditions ); } return $where; } } + diff --git a/php/WP_CLI/Iterators/Transform.php b/php/WP_CLI/Iterators/Transform.php index 3c6bdc3bd..ec1030f65 100644 --- a/php/WP_CLI/Iterators/Transform.php +++ b/php/WP_CLI/Iterators/Transform.php @@ -2,27 +2,17 @@ namespace WP_CLI\Iterators; -use IteratorIterator; - /** * Applies one or more callbacks to an item before returning it. - * - * @phpstan-extends IteratorIterator */ -class Transform extends IteratorIterator { +class Transform extends \IteratorIterator { - /** - * List of transformer callbacks. - * - * @var array - */ - private $transformers = []; + private $transformers = array(); public function add_transform( $fn ) { $this->transformers[] = $fn; } - #[\ReturnTypeWillChange] public function current() { $value = parent::current(); @@ -33,3 +23,4 @@ public function current() { return $value; } } + diff --git a/php/WP_CLI/Loggers/Base.php b/php/WP_CLI/Loggers/Base.php index d0ff5cdbb..cd5f478c8 100644 --- a/php/WP_CLI/Loggers/Base.php +++ b/php/WP_CLI/Loggers/Base.php @@ -2,10 +2,6 @@ namespace WP_CLI\Loggers; -use cli\Colors; -use WP_CLI; -use WP_CLI\Runner; - /** * Base logger class */ @@ -13,25 +9,10 @@ abstract class Base { protected $in_color = false; - /** - * Informational message. - * - * @param string $message Message to write. - */ abstract public function info( $message ); - /** - * Success message. - * - * @param string $message Message to write. - */ abstract public function success( $message ); - /** - * Warning message. - * - * @param string $message Message to write. - */ abstract public function warning( $message ); /** @@ -41,15 +22,14 @@ abstract public function warning( $message ); * @return Runner Instance of the runner class */ protected function get_runner() { - return WP_CLI::get_runner(); + return \WP_CLI::get_runner(); } /** * Write a message to STDERR, prefixed with "Debug: ". * * @param string $message Message to write. - * @param string|bool $group Organize debug message to a specific group. - * Use `false` for no group. + * @param string $group Organize debug message to a specific group. */ public function debug( $message, $group = false ) { static $start_time = null; @@ -63,7 +43,7 @@ public function debug( $message, $group = false ) { if ( true !== $debug && $group !== $debug ) { return; } - $time = round( microtime( true ) - ( defined( 'WP_CLI_START_MICROTIME' ) ? WP_CLI_START_MICROTIME : $start_time ), 3 ); + $time = round( microtime( true ) - ( defined( 'WP_CLI_START_MICROTIME' ) ? WP_CLI_START_MICROTIME : $start_time ), 3 ); $prefix = 'Debug'; if ( $group && true === $debug ) { $prefix = 'Debug (' . $group . ')'; @@ -89,12 +69,13 @@ protected function write( $handle, $str ) { * @param string $color Colorize label with a given color. * @param resource $handle Resource to write to. Defaults to STDOUT. */ - protected function _line( $message, $label, $color, $handle = STDOUT ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Used in third party extensions. + protected function _line( $message, $label, $color, $handle = STDOUT ) { if ( class_exists( 'cli\Colors' ) ) { - $label = Colors::colorize( "$color$label:%n", $this->in_color ); + $label = \cli\Colors::colorize( "$color$label:%n", $this->in_color ); } else { $label = "$label:"; } $this->write( $handle, "$label $message\n" ); } + } diff --git a/php/WP_CLI/Loggers/Execution.php b/php/WP_CLI/Loggers/Execution.php index 1fa76654a..a95c141b5 100644 --- a/php/WP_CLI/Loggers/Execution.php +++ b/php/WP_CLI/Loggers/Execution.php @@ -2,8 +2,6 @@ namespace WP_CLI\Loggers; -use WP_CLI; - /** * Execution logger captures all STDOUT and STDERR writes */ @@ -22,20 +20,20 @@ class Execution extends Regular { /** * @param bool $in_color Whether or not to Colorize strings. */ - public function __construct( $in_color = false ) { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Provides a default value. + function __construct( $in_color = false ) { parent::__construct( $in_color ); } /** - * Similar to error( $message ), but outputs $message in a red box. + * Similar to error( $message ), but outputs $message in a red box * - * @param array $message_lines Message to write. + * @param array $message Message to write. */ public function error_multi_line( $message_lines ) { $message = implode( "\n", $message_lines ); - $this->write( STDERR, WP_CLI::colorize( "%RError:%n\n$message\n" ) ); - $this->write( STDERR, WP_CLI::colorize( "%R---------%n\n\n" ) ); + $this->write( STDERR, \WP_CLI::colorize( "%RError:%n\n$message\n" ) ); + $this->write( STDERR, \WP_CLI::colorize( "%R---------%n\n\n" ) ); } /** @@ -59,7 +57,8 @@ protected function write( $handle, $str ) { * Starts output buffering, using a callback to capture output from `echo`, `print`, `printf` (which write to the output buffer 'php://output' rather than STDOUT). */ public function ob_start() { - ob_start( [ $this, 'ob_start_callback' ], 1 ); + // To ensure sequential output, give a chunk size of 1 (or 2 if PHP < 5.4 as 1 was a special value meaning a 4KB chunk) to `ob_start()`, so that each write gets flushed immediately. + ob_start( array( $this, 'ob_start_callback' ), version_compare( PHP_VERSION, '5.4.0', '<' ) ? 2 : 1 ); } /** diff --git a/php/WP_CLI/Loggers/Quiet.php b/php/WP_CLI/Loggers/Quiet.php index 6a9b7c3b4..e8de7be82 100644 --- a/php/WP_CLI/Loggers/Quiet.php +++ b/php/WP_CLI/Loggers/Quiet.php @@ -7,21 +7,13 @@ */ class Quiet extends Base { - /** - * @param bool $in_color Whether or not to Colorize strings. - */ - public function __construct( $in_color = false ) { - $this->in_color = $in_color; - } - /** * Informational messages aren't logged. * * @param string $message Message to write. - * @param bool $newline Optional. Whether to append a newline to the end of the message. Default true. */ - public function info( $message, $newline = true ) { - // Nothing. + public function info( $message ) { + // nothing } /** @@ -30,7 +22,7 @@ public function info( $message, $newline = true ) { * @param string $message Message to write. */ public function success( $message ) { - // Nothing. + // nothing } /** @@ -39,7 +31,7 @@ public function success( $message ) { * @param string $message Message to write. */ public function warning( $message ) { - // Nothing. + // nothing } /** @@ -48,18 +40,18 @@ public function warning( $message ) { * @param string $message Message to write. */ public function error( $message ) { - $this->_line( $message, 'Error', '%R', STDERR ); + $this->write( STDERR, \WP_CLI::colorize( "%RError:%n $message\n" ) ); } /** - * Similar to error( $message ), but outputs $message in a red box. + * Similar to error( $message ), but outputs $message in a red box * - * @param array $message_lines Message to write. + * @param array $message Message to write. */ public function error_multi_line( $message_lines ) { $message = implode( "\n", $message_lines ); - $this->_line( $message, 'Error', '%R', STDERR ); - $this->_line( '', '---------', '%R', STDERR ); + $this->write( STDERR, \WP_CLI::colorize( "%RError:%n\n$message\n" ) ); + $this->write( STDERR, \WP_CLI::colorize( "%R---------%n\n\n" ) ); } } diff --git a/php/WP_CLI/Loggers/Regular.php b/php/WP_CLI/Loggers/Regular.php index 3f53096ab..e5e420bd6 100644 --- a/php/WP_CLI/Loggers/Regular.php +++ b/php/WP_CLI/Loggers/Regular.php @@ -2,8 +2,6 @@ namespace WP_CLI\Loggers; -use cli\Colors; - /** * Default logger for success, warning, error, and standard messages. */ @@ -20,10 +18,9 @@ public function __construct( $in_color ) { * Write an informational message to STDOUT. * * @param string $message Message to write. - * @param bool $newline Optional. Whether to append a newline to the end of the message. Default true. */ - public function info( $message, $newline = true ) { - $this->write( STDOUT, $message . ( $newline ? "\n" : '' ) ); + public function info( $message ) { + $this->write( STDOUT, $message . "\n" ); } /** @@ -54,14 +51,14 @@ public function error( $message ) { } /** - * Similar to error( $message ), but outputs $message in a red box. + * Similar to error( $message ), but outputs $message in a red box * - * @param non-empty-array $message_lines Message to write. + * @param array $message Message to write. */ public function error_multi_line( $message_lines ) { - // Convert tabs to four spaces, as some shells will output the tabs as variable-length. + // convert tabs to four spaces, as some shells will output the tabs as variable-length $message_lines = array_map( - function ( $line ) { + function( $line ) { return str_replace( "\t", ' ', $line ); }, $message_lines @@ -69,17 +66,17 @@ function ( $line ) { $longest = max( array_map( 'strlen', $message_lines ) ); - // Write an empty line before the message. - $empty_line = Colors::colorize( '%w%1 ' . str_repeat( ' ', $longest ) . ' %n' ); + // write an empty line before the message + $empty_line = \cli\Colors::colorize( '%w%1 ' . str_repeat( ' ', $longest ) . ' %n' ); $this->write( STDERR, "\n\t$empty_line\n" ); foreach ( $message_lines as $line ) { $padding = str_repeat( ' ', $longest - strlen( $line ) ); - $line = Colors::colorize( "%w%1 $line $padding%n" ); + $line = \cli\Colors::colorize( "%w%1 $line $padding%n" ); $this->write( STDERR, "\t$line\n" ); } - // Write an empty line after the message. + // write an empty line after the message $this->write( STDERR, "\t$empty_line\n\n" ); } } diff --git a/php/WP_CLI/NoOp.php b/php/WP_CLI/NoOp.php index 90f884b71..4ac48c4a3 100644 --- a/php/WP_CLI/NoOp.php +++ b/php/WP_CLI/NoOp.php @@ -4,10 +4,6 @@ /** * Escape route for not doing anything. - * - * @method void display(bool $finish = false) - * @method void tick(int $increment = 1, ?string $msg = null) - * @method void finish() */ final class NoOp { @@ -19,3 +15,4 @@ public function __call( $method, $args ) { // do nothing } } + diff --git a/php/WP_CLI/PackageManagerEventSubscriber.php b/php/WP_CLI/PackageManagerEventSubscriber.php index 39f963cbf..251b2f4cd 100644 --- a/php/WP_CLI/PackageManagerEventSubscriber.php +++ b/php/WP_CLI/PackageManagerEventSubscriber.php @@ -2,58 +2,33 @@ namespace WP_CLI; -use Composer\DependencyResolver\Rule; -use Composer\EventDispatcher\EventSubscriberInterface; -use Composer\Installer\PackageEvent; +use \Composer\DependencyResolver\Rule; +use \Composer\EventDispatcher\EventSubscriberInterface; +use \Composer\Installer\PackageEvent; use Composer\Installer\PackageEvents; -use WP_CLI; +use \WP_CLI; /** * A Composer Event subscriber so we can keep track of what's happening inside Composer */ class PackageManagerEventSubscriber implements EventSubscriberInterface { - /** - * Get subscribed events. - * - * @return array - */ public static function getSubscribedEvents() { - return [ - PackageEvents::PRE_PACKAGE_INSTALL => 'pre_install', + return array( + PackageEvents::PRE_PACKAGE_INSTALL => 'pre_install', PackageEvents::POST_PACKAGE_INSTALL => 'post_install', - ]; + ); } - /** - * Pre-install operation message. - * - * @param \Composer\Installer\PackageEvent $event - * - * @return void - */ public static function pre_install( PackageEvent $event ) { $operation_message = $event->getOperation()->__toString(); WP_CLI::log( ' - ' . $operation_message ); } - /** - * Post-install operation log. - * - * @param \Composer\Installer\PackageEvent $event - * - * @return void - */ public static function post_install( PackageEvent $event ) { $operation = $event->getOperation(); - - // getReason() was removed in Composer v2 without replacement. - if ( ! method_exists( $operation, 'getReason' ) ) { - return; - } - $reason = $operation->getReason(); if ( $reason instanceof Rule ) { @@ -71,5 +46,7 @@ public static function post_install( PackageEvent $event ) { WP_CLI::log( sprintf( ' - Warning: %s', $composer_error ) ); } } + } + } diff --git a/php/WP_CLI/Path.php b/php/WP_CLI/Path.php deleted file mode 100644 index 2ed4b6eb4..000000000 --- a/php/WP_CLI/Path.php +++ /dev/null @@ -1,260 +0,0 @@ -#.*?$)|(?>//.*?$)|(?>/\*.*?\*/)|(?>\'(?:(?=(\\\\?))\1.)*?\')|(?>"(?:(?=(\\\\?))\2.)*?")|(?\b__FILE__\b)|(?\b__DIR__\b)%ms'; - - /** - * Check if a certain path is within a Phar archive. - * - * If no path is provided, the function checks whether the current WP_CLI instance is - * running from within a Phar archive. - * - * @param string|null $path Optional. Path to check. Defaults to null, which checks WP_CLI_ROOT. - * @return bool Whether path is within a Phar archive. - */ - public static function inside_phar( $path = null ) { - if ( null === $path ) { - if ( ! defined( 'WP_CLI_ROOT' ) ) { - return false; - } - - $path = WP_CLI_ROOT; - } - - return 0 === stripos( $path, self::PHAR_STREAM_PREFIX ); - } - - /** - * Determine whether a path is absolute. - * - * @param string $path - * @return bool - */ - public static function is_absolute( $path ) { - // Empty path is not absolute. - if ( '' === $path ) { - return false; - } - - // Windows drive letter + colon + slash or backslash. - if ( preg_match( '#^[A-Z]:[\\\\/]#i', $path ) ) { - return true; - } - - // UNC path (\\Server\Share). - if ( preg_match( '#^\\\\\\\\[^\\\\/]+[\\\\/][^\\\\/]+#', $path ) ) { - return true; - } - - // Unix root. - return isset( $path[0] ) && '/' === $path[0]; - } - - /** - * Expand tilde (~) in path to home directory. - * - * Expands paths that start with ~ to the current user's home directory. - * Only handles the current user's home directory (not ~username patterns). - * - * @param string $path Path that may contain a tilde. - * @return string Path with tilde expanded to home directory, or unchanged if tilde not at start or followed by username. - */ - public static function expand_tilde( $path ) { - if ( isset( $path[0] ) && '~' === $path[0] ) { - $home = self::get_home_dir(); - // Only expand if we can determine the home directory. - // Handle both "~" and "~/..." patterns (but not "~username"). - if ( ! empty( $home ) && ( 1 === strlen( $path ) || '/' === $path[1] ) ) { - $path = $home . substr( $path, 1 ); - } - // If followed by anything other than '/', or home is empty, leave it unchanged. - } - - return $path; - } - - /** - * Get the home directory. - * - * @return string - */ - public static function get_home_dir() { - $home = getenv( 'HOME' ); - if ( ! $home ) { - // In Windows $HOME may not be defined. - $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); - } - - return rtrim( $home, '/\\' ); - } - - /** - * Appends a trailing slash. - * - * @param string $value What to add the trailing slash to. - * @return string String with trailing slash added. - */ - public static function trailingslashit( $value ) { - if ( ! is_string( $value ) ) { - return '/'; - } - - return rtrim( $value, '/\\' ) . '/'; - } - - /** - * Check if a path is a PHP stream URL. - * - * @param string $path The resource path or URL. - * @return bool True if the path is a PHP stream URL, false otherwise. - */ - public static function is_stream( $path ) { - $scheme_separator = strpos( $path, '://' ); - - if ( false === $scheme_separator ) { - return false; - } - - $stream = strtolower( substr( $path, 0, $scheme_separator ) ); - - return in_array( $stream, stream_get_wrappers(), true ); - } - - /** - * Normalize a filesystem path. - * - * On Windows systems, replaces backslashes with forward slashes - * and forces upper-case drive letters. - * Allows for two leading slashes for Windows network shares, but - * ensures that all other duplicate slashes are reduced to a single one. - * Ensures upper-case drive letters on Windows systems. - * Allows for PHP file wrappers. - * - * @param string $path Path to normalize. - * @return string Normalized path. - */ - public static function normalize( $path ) { - $wrapper = ''; - if ( self::is_stream( $path ) ) { - list( $wrapper, $path ) = explode( '://', $path, 2 ); - $wrapper .= '://'; - } - $path = str_replace( '\\', '/', $path ); - $path = (string) preg_replace( '|(?<=.)/+|', '/', $path ); - if ( ':' === substr( $path, 1, 1 ) ) { - $path = ucfirst( $path ); - } - // Resolve single-dot path segments (e.g., /foo/./bar becomes /foo/bar). - $path = (string) preg_replace( '#/(?:\./)+#', '/', $path ); - if ( '/.' === substr( $path, -2 ) ) { - $path = substr( $path, 0, -1 ); - } - // Resolve leading ./ (e.g., ./foo/bar becomes foo/bar). - $path = (string) preg_replace( '#^(?:\./)+#', '', $path ); - // Collapse any duplicate slashes introduced by dot-segment resolution. - $path = (string) preg_replace( '|(?<=.)/+|', '/', $path ); - return $wrapper . $path; - } - - /** - * Get the file basename. - * - * @param string $path - * @param string $suffix - * @return string - */ - public static function basename( $path, $suffix = '' ) { - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode -- Format required by wordpress.org API. - return urldecode( \basename( str_replace( [ '%2F', '%5C' ], '/', urlencode( $path ) ), $suffix ) ); - } - - /** - * Get a Phar-safe version of a path. - * - * For paths inside a Phar, this strips the outer filesystem's location to - * reduce the path to what it needs to be within the Phar archive. - * - * Use the __FILE__ or __DIR__ constants as a starting point. - * - * @param string $path An absolute path that might be within a Phar. - * @return string A Phar-safe version of the path. - */ - public static function phar_safe( $path ) { - if ( ! self::inside_phar() ) { - return $path; - } - - return str_replace( - self::PHAR_STREAM_PREFIX . rtrim( WP_CLI_PHAR_PATH, '/' ) . '/', - self::PHAR_STREAM_PREFIX, - $path - ); - } - - /** - * Replace magic constants in some PHP source code. - * - * Replaces the __FILE__ and __DIR__ magic constants with the values they are - * supposed to represent at runtime. - * - * @param string $source The PHP code to manipulate. - * @param string $path The path to use instead of the magic constants. - * @return string Adapted PHP code. - */ - public static function replace_path_consts( $source, $path ) { - // Solve issue with Windows allowing single quotes in account names. - $file = addslashes( $path ); - - if ( file_exists( $file ) ) { - $file = (string) realpath( $file ); - } - - $dir = dirname( $file ); - - // Replace __FILE__ and __DIR__ constants with value of $file or $dir. - return (string) preg_replace_callback( - self::FILE_DIR_PATTERN, - static function ( $matches ) use ( $file, $dir ) { - if ( ! empty( $matches['file'] ) ) { - return "'{$file}'"; - } - - if ( ! empty( $matches['dir'] ) ) { - return "'{$dir}'"; - } - - return $matches[0]; - }, - $source - ); - } -} diff --git a/php/WP_CLI/Process.php b/php/WP_CLI/Process.php index c4dbce833..584f679b9 100644 --- a/php/WP_CLI/Process.php +++ b/php/WP_CLI/Process.php @@ -2,7 +2,7 @@ namespace WP_CLI; -use RuntimeException; +use WP_CLI\Utils; /** * Run a system process, and learn what happened. @@ -19,18 +19,18 @@ class Process { private $cwd; /** - * @var array|null Environment variables to set when running the command. + * @var array Environment variables to set when running the command. */ private $env; /** * @var array Descriptor spec for `proc_open()`. */ - private static $descriptors = [ + private static $descriptors = array( 0 => STDIN, - 1 => [ 'pipe', 'w' ], - 2 => [ 'pipe', 'w' ], - ]; + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); /** * @var bool Whether to log run time info or not. @@ -40,21 +40,21 @@ class Process { /** * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count. */ - public static $run_times = []; + public static $run_times = array(); /** - * @param string $command Command to execute. - * @param string|null $cwd Directory to execute the command in. - * @param array|null $env Environment variables to set when running the command. + * @param string $command Command to execute. + * @param string $cwd Directory to execute the command in. + * @param array $env Environment variables to set when running the command. * * @return Process */ - public static function create( $command, $cwd = null, $env = [] ) { - $proc = new self(); + public static function create( $command, $cwd = null, $env = array() ) { + $proc = new self; $proc->command = $command; - $proc->cwd = $cwd; - $proc->env = $env; + $proc->cwd = $cwd; + $proc->env = $env; return $proc; } @@ -67,74 +67,38 @@ private function __construct() {} * @return ProcessRun */ public function run() { - Utils\check_proc_available( 'Process::run' ); - $start_time = microtime( true ); - /** - * @var array $pipes - */ - $pipes = []; - if ( Utils\is_windows() ) { - // On Windows, leaving pipes open can cause hangs. - // Redirect output to files and close stdin. - $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); - $stderr_file = tempnam( sys_get_temp_dir(), 'behat-stderr-' ); - $descriptors = [ - 0 => [ 'pipe', 'r' ], - 1 => [ 'file', $stdout_file, 'a' ], - 2 => [ 'file', $stderr_file, 'a' ], - ]; - $proc = Utils\proc_open_compat( $this->command, $descriptors, $pipes, $this->cwd, $this->env ); - if ( $proc && isset( $pipes[0] ) ) { - fclose( $pipes[0] ); - } - } else { - $proc = Utils\proc_open_compat( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env ); - if ( $proc ) { - $stdout = stream_get_contents( $pipes[1] ); - fclose( $pipes[1] ); - $stderr = stream_get_contents( $pipes[2] ); - fclose( $pipes[2] ); - } else { - $stdout = ''; - $stderr = ''; - } - } + $proc = Utils\proc_open_compat( $this->command, self::$descriptors, $pipes, $this->cwd, $this->env ); - $return_code = $proc ? proc_close( $proc ) : -1; + $stdout = stream_get_contents( $pipes[1] ); + fclose( $pipes[1] ); - if ( Utils\is_windows() ) { - $stdout = (string) file_get_contents( $stdout_file ); - $stderr = (string) file_get_contents( $stderr_file ); - unlink( $stdout_file ); - unlink( $stderr_file ); + $stderr = stream_get_contents( $pipes[2] ); + fclose( $pipes[2] ); - // Normalize line endings. - $stdout = str_replace( "\r\n", "\n", $stdout ); - $stderr = str_replace( "\r\n", "\n", $stderr ); - } + $return_code = proc_close( $proc ); $run_time = microtime( true ) - $start_time; if ( self::$log_run_times ) { if ( ! isset( self::$run_times[ $this->command ] ) ) { - self::$run_times[ $this->command ] = [ 0, 0 ]; + self::$run_times[ $this->command ] = array( 0, 0 ); } self::$run_times[ $this->command ][0] += $run_time; - ++self::$run_times[ $this->command ][1]; + self::$run_times[ $this->command ][1]++; } return new ProcessRun( - [ - 'stdout' => $stdout, - 'stderr' => $stderr, + array( + 'stdout' => $stdout, + 'stderr' => $stderr, 'return_code' => $return_code, - 'command' => $this->command, - 'cwd' => $this->cwd, - 'env' => $this->env, - 'run_time' => $run_time, - ] + 'command' => $this->command, + 'cwd' => $this->cwd, + 'env' => $this->env, + 'run_time' => $run_time, + ) ); } @@ -146,8 +110,9 @@ public function run() { public function run_check() { $r = $this->run(); - if ( $r->return_code ) { - throw new RuntimeException( $r ); + // $r->STDERR is incorrect, but kept incorrect for backwards-compat + if ( $r->return_code || ! empty( $r->STDERR ) ) { + throw new \RuntimeException( $r ); } return $r; @@ -162,28 +127,8 @@ public function run_check() { public function run_check_stderr() { $r = $this->run(); - if ( $r->return_code ) { - throw new RuntimeException( $r ); - } - - if ( ! empty( $r->stderr ) ) { - // If the only thing that STDERR caught was the Requests deprecated message, ignore it. - // This is a temporary fix until we have a better solution for dealing with Requests - // as a dependency shared between WP Core and WP-CLI. - $stderr_lines = array_filter( explode( "\n", $r->stderr ) ); - if ( 1 === count( $stderr_lines ) ) { - $stderr_line = $stderr_lines[0]; - if ( - false !== strpos( - $stderr_line, - 'The PSR-0 `Requests_...` class names in the Request library are deprecated.' - ) - ) { - return $r; - } - } - - throw new RuntimeException( $r ); + if ( $r->return_code || ! empty( $r->stderr ) ) { + throw new \RuntimeException( $r ); } return $r; diff --git a/php/WP_CLI/ProcessRun.php b/php/WP_CLI/ProcessRun.php index 63e96b6aa..96b4c80b6 100644 --- a/php/WP_CLI/ProcessRun.php +++ b/php/WP_CLI/ProcessRun.php @@ -6,60 +6,43 @@ * Results of an executed command. */ class ProcessRun { - /** - * The full command executed by the system. - * - * @var string + * @var string The full command executed by the system. */ public $command; /** - * Captured output from the process' STDOUT. - * - * @var string + * @var string Captured output from the process' STDOUT. */ public $stdout; /** - * Captured output from the process' STDERR. - * - * @var string + * @var string Captured output from the process' STDERR. */ public $stderr; /** - * The path of the working directory for the process or NULL if not specified. - * - * This defaults to current working directory. - * - * @var string|null + * @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory). */ public $cwd; /** - * Environment variables set for this process. - * - * @var array + * @var array Environment variables set for this process. */ public $env; /** - * Exit code of the process. - * - * @var int + * @var int Exit code of the process. */ public $return_code; /** - * The run time of the process. - * - * @var float + * @var float The run time of the process. */ public $run_time; /** - * @param array $props Properties of executed command. + * @var array $props Properties of executed command. */ public function __construct( $props ) { foreach ( $props as $key => $value ) { @@ -81,4 +64,5 @@ public function __toString() { return $out; } + } diff --git a/php/WP_CLI/RequestsLibrary.php b/php/WP_CLI/RequestsLibrary.php deleted file mode 100644 index e3d0aaa85..000000000 --- a/php/WP_CLI/RequestsLibrary.php +++ /dev/null @@ -1,300 +0,0 @@ - - */ - const VALID_VERSIONS = [ self::VERSION_V1, self::VERSION_V2 ]; - - /** - * Requests library bundled with WordPress Core being used. - * - * @var string - */ - const SOURCE_WP_CORE = 'wp-core'; - - /** - * Requests library bundled with WP-CLI being used. - * - * @var string - */ - const SOURCE_WP_CLI = 'wp-cli'; - - /** - * Array of valid source for the Requests library. - * - * @var array - */ - const VALID_SOURCES = [ self::SOURCE_WP_CORE, self::SOURCE_WP_CLI ]; - - /** - * Class name of the Requests main class for v1. - * - * @var string - */ - const CLASS_NAME_V1 = '\Requests'; - - /** - * Class name of the Requests main class for v2. - * - * @var string - */ - const CLASS_NAME_V2 = '\WpOrg\Requests\Requests'; - - /** - * Version of the Requests library being used. - * - * @var string - */ - private static $version = self::VERSION_V2; - - /** - * Source of the Requests library being used. - * - * @var string - */ - private static $source = self::SOURCE_WP_CLI; - - /** - * Class name of the Requests library being used. - * - * @var string - */ - private static $class_name = self::CLASS_NAME_V2; - - /** - * Check if the current version is v1. - * - * @return bool Whether the current version is v1. - */ - public static function is_v1() { - return self::get_version() === self::VERSION_V1; - } - - /** - * Check if the current version is v2. - * - * @return bool Whether the current version is v2. - */ - public static function is_v2() { - return self::get_version() === self::VERSION_V2; - } - - /** - * Check if the current source for the Requests library is WordPress Core. - * - * @return bool Whether the current source is WordPress Core. - */ - public static function is_core() { - return self::get_source() === self::SOURCE_WP_CORE; - } - - /** - * Check if the current source for the Requests library is WP-CLI. - * - * @return bool Whether the current source is WP-CLI. - */ - public static function is_cli() { - return self::get_source() === self::SOURCE_WP_CLI; - } - - /** - * Get the current version. - * - * @return string The current version. - */ - public static function get_version() { - return self::$version; - } - - /** - * Set the version of the library. - * - * @param string $version The version to set. - * @throws RuntimeException if the version is invalid. - */ - public static function set_version( $version ) { - if ( ! is_string( $version ) ) { - throw new RuntimeException( 'RequestsLibrary::$version must be a string.' ); - } - - if ( ! in_array( $version, self::VALID_VERSIONS, true ) ) { - throw new RuntimeException( - sprintf( - 'Invalid RequestsLibrary::$version, must be one of: %s.', - implode( ', ', self::VALID_VERSIONS ) - ) - ); - } - - WP_CLI::debug( 'Setting RequestsLibrary::$version to ' . $version, 'bootstrap' ); - - self::$version = $version; - } - - /** - * Get the current class name. - * - * @return string The current class name. - */ - public static function get_class_name() { - return self::$class_name; - } - - /** - * Set the class name for the library. - * - * @param string $class_name The class name to set. - */ - public static function set_class_name( $class_name ) { - if ( ! is_string( $class_name ) ) { - throw new RuntimeException( 'RequestsLibrary::$class_name must be a string.' ); - } - - WP_CLI::debug( 'Setting RequestsLibrary::$class_name to ' . $class_name, 'bootstrap' ); - - self::$class_name = $class_name; - } - - /** - * Get the current source. - * - * @return string The current source. - */ - public static function get_source() { - return self::$source; - } - - /** - * Set the source of the library. - * - * @param string $source The source to set. - * @throws RuntimeException if the source is invalid. - */ - public static function set_source( $source ) { - if ( ! is_string( $source ) ) { - throw new RuntimeException( 'RequestsLibrary::$source must be a string.' ); - } - - if ( ! in_array( $source, self::VALID_SOURCES, true ) ) { - throw new RuntimeException( - sprintf( - 'Invalid RequestsLibrary::$source, must be one of: %s.', - implode( ', ', self::VALID_SOURCES ) - ) - ); - } - - WP_CLI::debug( 'Setting RequestsLibrary::$source to ' . $source, 'bootstrap' ); - - self::$source = $source; - } - - /** - * Check if a given exception was issued by the Requests library. - * - * This is used because we cannot easily catch multiple different exception - * classes with PHP 5.6. Because of that, we catch generic exceptions, check if - * they match the Requests library, and re-throw them if they do not. - * - * @param \Exception|\Requests_Exception|\WpOrg\Requests\Exception $exception Exception to check. - * @return bool Whether the provided exception was issued by the Requests library. - * - * @phpstan-assert-if-true \Requests_Exception|\WpOrg\Requests\Exception $exception - */ - public static function is_requests_exception( Exception $exception ) { - return is_a( $exception, '\Requests_Exception' ) - || is_a( $exception, '\WpOrg\Requests\Exception' ); - } - - /** - * Register the autoloader for the Requests library. - * - * This checks for the detected setup and register the corresponding - * autoloader if it is still needed. - */ - public static function register_autoloader() { - $includes_path = defined( 'WPINC' ) ? WPINC : 'wp-includes'; - - if ( self::is_v1() && ! class_exists( self::CLASS_NAME_V1 ) ) { - if ( self::is_core() ) { - require_once ABSPATH . $includes_path . '/class-requests.php'; - } else { - require_once WP_CLI_VENDOR_DIR . '/rmccue/requests/library/Requests.php'; - } - // @phpstan-ignore staticMethod.deprecatedClass - \Requests::register_autoloader(); - } - - if ( self::is_v2() && ! class_exists( self::CLASS_NAME_V2 ) ) { - if ( self::is_core() ) { - require_once ABSPATH . $includes_path . '/Requests/Autoload.php'; - } else { - self::maybe_define_wp_cli_root(); - if ( file_exists( WP_CLI_ROOT . '/bundle/rmccue/requests/src/Autoload.php' ) ) { - require_once WP_CLI_ROOT . '/bundle/rmccue/requests/src/Autoload.php'; - } else { - require_once WP_CLI_VENDOR_DIR . '/rmccue/requests/src/Autoload.php'; - } - } - \WpOrg\Requests\Autoload::register(); - } - } - - /** - * Get the path to the bundled certificate. - * - * @return string The path to the bundled certificate. - */ - public static function get_bundled_certificate_path() { - if ( self::is_core() ) { - $includes_path = defined( 'WPINC' ) ? WPINC : 'wp-includes'; - return ABSPATH . $includes_path . '/certificates/ca-bundle.crt'; - } elseif ( self::is_v1() ) { - return WP_CLI_VENDOR_DIR . '/rmccue/requests/library/Requests/Transport/cacert.pem'; - } else { - self::maybe_define_wp_cli_root(); - if ( file_exists( WP_CLI_ROOT . '/bundle/rmccue/requests/certificates/cacert.pem' ) ) { - return WP_CLI_ROOT . '/bundle/rmccue/requests/certificates/cacert.pem'; - } - return WP_CLI_VENDOR_DIR . '/rmccue/requests/certificates/cacert.pem'; - } - } - - /** - * Define WP_CLI_ROOT if it is not already defined. - */ - private static function maybe_define_wp_cli_root() { - if ( ! defined( 'WP_CLI_ROOT' ) ) { - define( 'WP_CLI_ROOT', dirname( dirname( __DIR__ ) ) ); - } - } -} diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 7d78403f6..50a6eae5c 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -3,139 +3,36 @@ namespace WP_CLI; use WP_CLI; +use WP_CLI\Utils; +use WP_CLI\Dispatcher; use WP_CLI\Dispatcher\CompositeCommand; -use WP_CLI\Dispatcher\Subcommand; -use WP_Error; /** * Performs the execution of a command. * - * @property-read string $system_config_path - * @property-read string $global_config_path - * @property-read string $project_config_path - * @property-read array $config - * @property-read array $extra_config - * @property-read ContextManager $context_manager - * @property-read string $alias - * @property-read array $aliases - * @property-read array $raw_aliases - * @property-read array $arguments - * @property-read array $assoc_args - * @property-read array $runtime_config - * @property-read bool $colorize - * @property-read array $early_invoke - * @property-read string $system_config_path_debug - * @property-read string $global_config_path_debug - * @property-read string $project_config_path_debug - * @property-read array $required_files - * * @package WP_CLI */ class Runner { - /** - * List of byte-order marks (BOMs) to detect. - * - * @var array - */ - const BYTE_ORDER_MARKS = [ - 'UTF-8' => "\xEF\xBB\xBF", - 'UTF-16 (BE)' => "\xFE\xFF", - 'UTF-16 (LE)' => "\xFF\xFE", - ]; - - /** - * Path to the system-wide configuration file. - * - * @var string|false - */ - private $system_config_path; - - /** - * Path to the global configuration file. - * - * @var string|false - */ - private $global_config_path; - - /** - * Path to the project-specific configuration file. - * - * @var string|false - */ - private $project_config_path; + private $global_config_path, $project_config_path; - /** @var array */ - private $config = [ - 'disabled_commands' => [], - ]; - /** @var array */ - private $extra_config; + private $config, $extra_config; - /** - * Context manager instance. - * - * @var ContextManager - */ - private $context_manager; - - /** @var string|null */ private $alias; - /** @var array>|string> */ - private $aliases = []; + private $aliases; - /** - * Raw aliases configuration. - * - * @var array - */ - private $raw_aliases; - - /** @var array */ - private $arguments = []; - /** @var array|int|string|true> */ - private $assoc_args = []; - /** @var array */ - private $runtime_config; + private $arguments, $assoc_args, $runtime_config; - /** - * Whether or not to colorize output. - * - * @var bool - */ private $colorize = false; - /** @var array>> */ - private $early_invoke = []; + private $_early_invoke = array(); - /** - * Debug message for system configuration path. - * - * @var string - */ - private $system_config_path_debug; + private $_global_config_path_debug; - /** - * Debug message for global configuration path. - * - * @var string - */ - private $global_config_path_debug; + private $_project_config_path_debug; - /** - * Debug message for project configuration path. - * - * @var string - */ - private $project_config_path_debug; - - /** - * List of required files. - * - * @var array - */ - private $required_files; + private $_required_files; public function __get( $key ) { if ( '_' === $key[0] ) { @@ -145,52 +42,33 @@ public function __get( $key ) { return $this->$key; } - public function register_context_manager( ContextManager $context_manager ) { - $this->context_manager = $context_manager; - } - /** * Register a command for early invocation, generally before WordPress loads. * * @param string $when Named execution hook - * @param Subcommand $command + * @param WP_CLI\Dispatcher\Subcommand $command */ public function register_early_invoke( $when, $command ) { - $cmd_path = array_slice( Dispatcher\get_path( $command ), 1 ); - $command_name = implode( ' ', $cmd_path ); - WP_CLI::debug( "Attaching command '{$command_name}' to hook {$when}", 'bootstrap' ); - $this->early_invoke[ $when ][] = $cmd_path; - if ( $command->get_alias() ) { - array_pop( $cmd_path ); - $cmd_path[] = $command->get_alias(); - $alias_name = implode( ' ', $cmd_path ); - WP_CLI::debug( "Attaching command alias '{$alias_name}' to hook {$when}", 'bootstrap' ); - $this->early_invoke[ $when ][] = $cmd_path; - } + $this->_early_invoke[ $when ][] = array_slice( Dispatcher\get_path( $command ), 1 ); } /** * Perform the early invocation of a command. * * @param string $when Named execution hook - * - * @phpstan-impure */ - private function do_early_invoke( $when ): void { - WP_CLI::debug( "Executing hook: {$when}", 'hooks' ); - if ( ! isset( $this->early_invoke[ $when ] ) ) { + private function do_early_invoke( $when ) { + if ( ! isset( $this->_early_invoke[ $when ] ) ) { return; } // Search the value of @when from the command method. - // Use 'none' for autocorrect to avoid suggesting wrong alternatives - // for commands that may be registered later (e.g., via after_wp_load). $real_when = ''; - $r = $this->find_command_to_run( $this->arguments, 'none' ); + $r = $this->find_command_to_run( $this->arguments ); if ( is_array( $r ) ) { list( $command, $final_args, $cmd_path ) = $r; - foreach ( $this->early_invoke as $_when => $_path ) { + foreach ( $this->_early_invoke as $_when => $_path ) { foreach ( $_path as $cmd ) { if ( $cmd === $cmd_path ) { $real_when = $_when; @@ -199,12 +77,10 @@ private function do_early_invoke( $when ): void { } } - /** @var array> $invoke_cmds */ - $invoke_cmds = (array) $this->early_invoke[ $when ]; - foreach ( (array) $invoke_cmds as $path ) { + foreach ( $this->_early_invoke[ $when ] as $path ) { if ( $this->cmd_starts_with( $path ) ) { - if ( empty( $real_when ) || $real_when === $when ) { - $this->run_command_and_exit(); + if ( empty( $real_when ) || ( $real_when && $real_when === $when ) ) { + $this->_run_command_and_exit(); } } } @@ -213,88 +89,23 @@ private function do_early_invoke( $when ): void { /** * Get the path to the global configuration YAML file. * - * @param bool $create_config_file Optional. If a config file doesn't exist, - * should it be created? Defaults to false. - * * @return string|false */ - public function get_global_config_path( $create_config_file = false ) { - $wp_cli_config_path = (string) getenv( 'WP_CLI_CONFIG_PATH' ); + public function get_global_config_path() { - if ( $wp_cli_config_path ) { - $config_path = $wp_cli_config_path; - $this->global_config_path_debug = 'Using global config from WP_CLI_CONFIG_PATH env var: ' . $config_path; + if ( getenv( 'WP_CLI_CONFIG_PATH' ) ) { + $config_path = getenv( 'WP_CLI_CONFIG_PATH' ); + $this->_global_config_path_debug = 'Using global config from WP_CLI_CONFIG_PATH env var: ' . $config_path; } else { - $config_path = Path::get_home_dir() . '/.wp-cli/config.yml'; - $this->global_config_path_debug = 'Using default global config: ' . $config_path; - } - - // If global config doesn't exist create one. - if ( true === $create_config_file && ! file_exists( $config_path ) ) { - $this->global_config_path_debug = "Default global config doesn't exist, creating one in {$config_path}"; - - $dir = dirname( $config_path ); - - if ( ! is_dir( $dir ) ) { - mkdir( $dir, 0755, true ); - } - - touch( $config_path ); - - if ( file_exists( $config_path ) ) { - WP_CLI::debug( "Default global config does not exist, creating one in $config_path" ); - } + $config_path = Utils\get_home_dir() . '/.wp-cli/config.yml'; + $this->_global_config_path_debug = 'Using default global config: ' . $config_path; } if ( is_readable( $config_path ) ) { return $config_path; } - $this->global_config_path_debug = 'No readable global config found'; - - return false; - } - - /** - * Get the path to the system-wide configuration YAML file. - * - * @return string|false - */ - public function get_system_config_path() { - // Allow override via environment variable - $env_path = getenv( 'WP_CLI_SYSTEM_SETTINGS_PATH' ); - if ( $env_path ) { - $config_path = $env_path; - $this->system_config_path_debug = 'Using system config from WP_CLI_SYSTEM_SETTINGS_PATH env var: ' . $config_path; - if ( is_readable( $config_path ) ) { - return $config_path; - } - $this->system_config_path_debug = 'System config path from WP_CLI_SYSTEM_SETTINGS_PATH not readable: ' . $config_path; - return false; - } - - // Determine default path based on OS - if ( Utils\is_windows() ) { - // Windows: C:\ProgramData\wp-cli\config.yml - $program_data = getenv( 'ProgramData' ); - if ( ! $program_data ) { - $program_data = 'C:' . DIRECTORY_SEPARATOR . 'ProgramData'; - } - $config_path = $program_data . DIRECTORY_SEPARATOR . 'wp-cli' . DIRECTORY_SEPARATOR . 'config.yml'; - } elseif ( 'Darwin' === PHP_OS ) { - // macOS: /Library/Application Support/WP-CLI/config.yml - $config_path = '/Library/Application Support/WP-CLI/config.yml'; - } else { - // Linux and others: /etc/wp-cli/config.yml - $config_path = '/etc/wp-cli/config.yml'; - } - - if ( is_readable( $config_path ) ) { - $this->system_config_path_debug = 'Using system config: ' . $config_path; - return $config_path; - } - - $this->system_config_path_debug = 'No readable system config found'; + $this->_global_config_path_debug = 'No readable global config found'; return false; } @@ -307,32 +118,32 @@ public function get_system_config_path() { * @return string|false */ public function get_project_config_path() { - $config_files = [ + $config_files = array( 'wp-cli.local.yml', 'wp-cli.yml', - ]; + ); // Stop looking upward when we find we have emerged from a subdirectory - // installation into a parent installation + // install into a parent install $project_config_path = Utils\find_file_upward( $config_files, - (string) getcwd(), - static function ( $dir ) { + getcwd(), + function ( $dir ) { static $wp_load_count = 0; - $wp_load_path = $dir . DIRECTORY_SEPARATOR . 'wp-load.php'; + $wp_load_path = $dir . DIRECTORY_SEPARATOR . 'wp-load.php'; if ( file_exists( $wp_load_path ) ) { - ++$wp_load_count; + ++ $wp_load_count; } return $wp_load_count > 1; } ); - if ( null === $project_config_path ) { - $this->project_config_path_debug = 'No project config found'; - return false; + $this->_project_config_path_debug = 'No project config found'; + + if ( ! empty( $project_config_path ) ) { + $this->_project_config_path_debug = 'Using project config: ' . $project_config_path; } - $this->project_config_path_debug = 'Using project config: ' . $project_config_path; return $project_config_path; } @@ -342,42 +153,32 @@ static function ( $dir ) { * @return string */ public function get_packages_dir_path() { - $packages_dir = (string) Utils\get_env_or_config( 'WP_CLI_PACKAGES_DIR' ); - if ( $packages_dir ) { - $packages_dir = Path::expand_tilde( $packages_dir ); - if ( ! Path::is_absolute( $packages_dir ) ) { - $cwd = getcwd(); - if ( $cwd ) { - $packages_dir = $cwd . '/' . $packages_dir; - } - } - $packages_dir = Path::trailingslashit( $packages_dir ); + if ( getenv( 'WP_CLI_PACKAGES_DIR' ) ) { + $packages_dir = Utils\trailingslashit( getenv( 'WP_CLI_PACKAGES_DIR' ) ); } else { - $packages_dir = Path::get_home_dir() . '/.wp-cli/packages/'; + $packages_dir = Utils\get_home_dir() . '/.wp-cli/packages/'; } - - return Path::normalize( $packages_dir ); + return $packages_dir; } /** - * Attempts to find the path to the WP installation inside index.php + * Attempts to find the path to the WP install inside index.php * * @param string $index_path * @return string|false */ private static function extract_subdir_path( $index_path ) { - $index_code = (string) file_get_contents( $index_path ); + $index_code = file_get_contents( $index_path ); if ( ! preg_match( '|^\s*require\s*\(?\s*(.+?)/wp-blog-header\.php([\'"])|m', $index_code, $matches ) ) { return false; } $wp_path_src = $matches[1] . $matches[2]; - $wp_path_src = Path::replace_path_consts( $wp_path_src, $index_path ); - - $wp_path = eval( "return $wp_path_src;" ); // phpcs:ignore Squiz.PHP.Eval.Discouraged + $wp_path_src = Utils\replace_path_consts( $wp_path_src, $index_path ); + $wp_path = eval( "return $wp_path_src;" ); - if ( ! Path::is_absolute( $wp_path ) ) { + if ( ! Utils\is_path_absolute( $wp_path ) ) { $wp_path = dirname( $index_path ) . "/$wp_path"; } @@ -388,35 +189,23 @@ private static function extract_subdir_path( $index_path ) { * Find the directory that contains the WordPress files. * Defaults to the current working dir. * - * @return string An absolute path. + * @return string An absolute path */ - public function find_wp_root() { - if ( isset( $this->config['path'] ) && - ( is_bool( $this->config['path'] ) || empty( $this->config['path'] ) ) - ) { - WP_CLI::error( 'The --path parameter cannot be empty when provided.' ); - } - + private function find_wp_root() { if ( ! empty( $this->config['path'] ) ) { - /** - * @var string $path - */ $path = $this->config['path']; - - $path = Path::expand_tilde( $path ); - - if ( ! Path::is_absolute( $path ) ) { + if ( ! Utils\is_path_absolute( $path ) ) { $path = getcwd() . '/' . $path; } - return Path::normalize( $path ); + return $path; } - if ( $this->cmd_starts_with( [ 'core', 'download' ] ) ) { - return (string) getcwd(); + if ( $this->cmd_starts_with( array( 'core', 'download' ) ) ) { + return getcwd(); } - $dir = (string) getcwd(); + $dir = getcwd(); while ( is_readable( $dir ) ) { if ( file_exists( "$dir/wp-load.php" ) ) { @@ -424,8 +213,7 @@ public function find_wp_root() { } if ( file_exists( "$dir/index.php" ) ) { - $path = self::extract_subdir_path( "$dir/index.php" ); - if ( ! empty( $path ) ) { + if ( $path = self::extract_subdir_path( "$dir/index.php" ) ) { return $path; } } @@ -436,8 +224,6 @@ public function find_wp_root() { } $dir = $parent_dir; } - - return (string) getcwd(); } /** @@ -446,23 +232,7 @@ public function find_wp_root() { * @param string $path */ private static function set_wp_root( $path ) { - if ( ! defined( 'ABSPATH' ) ) { - $normalized = Path::normalize( Path::trailingslashit( $path ) ); - // Adjust Windows-style paths starting with drive letter + forward slash (C:/) so that - // WordPress core's path_is_absolute() recognizes them as absolute on Windows. - if ( preg_match( '#^[A-Z]:/#i', $normalized ) ) { - $normalized = preg_replace( '#^([A-Z]):/#i', '$1:\\\\', $normalized ); - } - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Declaring a WP native constant. - define( 'ABSPATH', $normalized ); - } elseif ( ! is_null( $path ) ) { - WP_CLI::error_multi_line( - [ - 'The --path parameter cannot be used when ABSPATH is already defined elsewhere', - 'ABSPATH is defined as: "' . ABSPATH . '"', - ] - ); - } + define( 'ABSPATH', Utils\trailingslashit( $path ) ); WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH, 'bootstrap' ); $_SERVER['DOCUMENT_ROOT'] = realpath( $path ); @@ -481,161 +251,62 @@ private static function guess_url( $assoc_args ) { if ( isset( $assoc_args['url'] ) ) { $url = $assoc_args['url']; - if ( true === $url ) { WP_CLI::warning( 'The --url parameter expects a value.' ); - return false; - } elseif ( is_string( $url ) && ! Utils\parse_url( $url, PHP_URL_HOST ) ) { - WP_CLI::warning( "The --url parameter value '{$url}' is not valid. Check for typos in the protocol, e.g. 'http://' not 'http:/'." ); - return false; } + } + if ( isset( $url ) ) { return $url; } return false; } - /** - * Checks if the arguments passed to the WP-CLI binary start with the specified prefix. - * - * @param array $prefix An array of strings specifying the expected start of the arguments passed to the WP-CLI binary. - * For example, `['user', 'list']` checks if the arguments passed to the WP-CLI binary start with `user list`. - * - * @return bool `true` if the arguments passed to the WP-CLI binary start with the specified prefix, `false` otherwise. - */ - private function cmd_starts_with( $prefix ): bool { - return array_slice( (array) $this->arguments, 0, count( $prefix ) ) === $prefix; + private function cmd_starts_with( $prefix ) { + return array_slice( $this->arguments, 0, count( $prefix ) ) === $prefix; } /** * Given positional arguments, find the command to execute. * * @param array $args - * @param string $autocorrect Whether to autocorrect commands based on suggestions. - * @return array{0: CompositeCommand, 1: array, 2: array}|string Command, args, and path on success; error message on failure - * - * @phpstan-param 'none'|'confirm'|'auto' $autocorrect + * @return array|string Command, args, and path on success; error message on failure */ - public function find_command_to_run( $args, $autocorrect = 'none' ) { - $command = WP_CLI::get_root_command(); + public function find_command_to_run( $args ) { + $command = \WP_CLI::get_root_command(); WP_CLI::do_hook( 'find_command_to_run_pre' ); - $cmd_path = []; + $cmd_path = array(); while ( ! empty( $args ) && $command->can_have_subcommands() ) { $cmd_path[] = $args[0]; - $full_name = implode( ' ', $cmd_path ); + $full_name = implode( ' ', $cmd_path ); $subcommand = $command->find_subcommand( $args ); if ( ! $subcommand ) { if ( count( $cmd_path ) > 1 ) { - $child = array_pop( $cmd_path ); + $child = array_pop( $cmd_path ); $parent_name = implode( ' ', $cmd_path ); - $suggestion = $this->get_subcommand_suggestion( $child, $command ); - - if ( 'network' === $parent_name && 'option' === $child ) { - $suggestion = 'meta'; - } - - $error = sprintf( - "'%s' is not a registered subcommand of '%s'. See 'wp help %s' for available subcommands.", + $suggestion = $this->get_subcommand_suggestion( $child, $command ); + return sprintf( + "'%s' is not a registered subcommand of '%s'. See 'wp help %s' for available subcommands.%s", $child, $parent_name, - $parent_name + $parent_name, + ! empty( $suggestion ) ? PHP_EOL . "Did you mean '{$suggestion}'?" : '' ); - - if ( ! empty( $suggestion ) ) { - $suggestion_text = "Did you mean '{$suggestion}'?"; - - if ( 'none' !== $autocorrect ) { - $suggested_command_to_run = $this->find_command_to_run( explode( ' ', "$parent_name $suggestion" ) ); - - if ( is_array( $suggested_command_to_run ) ) { - // Override potentially misspelled cmd with the corrected one. - $this->arguments = $suggested_command_to_run[2]; - - if ( 'auto' === $autocorrect ) { - return $suggested_command_to_run; - } - - WP_CLI::warning( $error ); - WP_CLI::confirm( $suggestion_text ); - return $suggested_command_to_run; - } - } - - return $error . PHP_EOL . $suggestion_text; - } - - return $error; } $suggestion = $this->get_subcommand_suggestion( $full_name, $command ); - // If the functions are available, it means WordPress is available - // and has already been loaded. - if ( function_exists( '\taxonomy_exists' ) ) { - if ( \taxonomy_exists( $cmd_path[0] ) ) { - $suggestion = 'wp term '; - } elseif ( \post_type_exists( $cmd_path[0] ) ) { - $suggestion = "wp post --post_type={$cmd_path[0]} "; - } - } - - $error = sprintf( - "'%s' is not a registered wp command. See 'wp help' for available commands.", - $full_name + return sprintf( + "'%s' is not a registered wp command. See 'wp help' for available commands.%s", + $full_name, + ! empty( $suggestion ) ? PHP_EOL . "Did you mean '{$suggestion}'?" : '' ); - - if ( ! empty( $suggestion ) ) { - $suggestion_text = "Did you mean '{$suggestion}'?"; - - if ( 'none' !== $autocorrect ) { - if ( 'help' === $suggestion ) { - // This was a typo suggestion for a help command, so find command without 'help' - // and then prepend 'help' again for the corrected command. - $suggested_command_to_run = $this->find_command_to_run( $args, 'auto' ); - if ( is_array( $suggested_command_to_run ) ) { - $this->arguments = array_merge( [ $suggestion ], $args ); - - $suggested_command_to_run = $this->find_command_to_run( $this->arguments, 'auto' ); - } - } - - if ( ! isset( $suggested_command_to_run ) || ! is_array( $suggested_command_to_run ) ) { - $suggested_command_to_run = $this->find_command_to_run( array_merge( [ $suggestion ], $args ), 'auto' ); - - if ( is_array( $suggested_command_to_run ) ) { - $this->arguments = $suggested_command_to_run[2]; - } - } - - if ( ! is_array( $suggested_command_to_run ) ) { - $suggested_command_to_run = $this->find_command_to_run( [ $suggestion ], 'auto' ); - - if ( is_array( $suggested_command_to_run ) ) { - $this->arguments = $suggested_command_to_run[2]; - } - } - - if ( is_array( $suggested_command_to_run ) ) { - if ( 'auto' === $autocorrect ) { - return $suggested_command_to_run; - } - - WP_CLI::warning( $error ); - WP_CLI::confirm( $suggestion_text ); - return $suggested_command_to_run; - } - } - - return $error . PHP_EOL . $suggestion_text; - } - - return $error; } if ( $this->is_command_disabled( $subcommand ) ) { @@ -648,7 +319,7 @@ public function find_command_to_run( $args, $autocorrect = 'none' ) { $command = $subcommand; } - return [ $command, $args, $cmd_path ]; + return array( $command, $args, $cmd_path ); } /** @@ -658,13 +329,11 @@ public function find_command_to_run( $args, $autocorrect = 'none' ) { * @param array $assoc_args Associative arguments for the command. * @param array $options Configuration options for the function. */ - public function run_command( $args, $assoc_args = [], $options = [] ) { - WP_CLI::do_hook( 'before_run_command', $args, $assoc_args, $options ); - + public function run_command( $args, $assoc_args = array(), $options = array() ) { if ( ! empty( $options['back_compat_conversions'] ) ) { list( $args, $assoc_args ) = self::back_compat_conversions( $args, $assoc_args ); } - $r = $this->find_command_to_run( $args, Utils\get_env_or_config( 'WP_CLI_AUTOCORRECT' ) ? 'auto' : 'confirm' ); + $r = $this->find_command_to_run( $args ); if ( is_string( $r ) ) { WP_CLI::error( $r ); } @@ -673,7 +342,7 @@ public function run_command( $args, $assoc_args = [], $options = [] ) { $name = implode( ' ', $cmd_path ); - $extra_args = []; + $extra_args = array(); if ( isset( $this->extra_config[ $name ] ) ) { $extra_args = $this->extra_config[ $name ]; @@ -681,15 +350,9 @@ public function run_command( $args, $assoc_args = [], $options = [] ) { WP_CLI::debug( 'Running command: ' . $name, 'bootstrap' ); try { - $command->invoke( $final_args, $assoc_args, (array) $extra_args ); - } catch ( ExitException $e ) { - // Re-throw control-flow exceptions so callers can handle exit codes/output. - throw $e; - } catch ( \Exception $e ) { - // Catch exceptions but not Error types, as Error types represent - // fatal errors that should be handled by ShutdownHandler for - // helpful plugin/theme skip suggestions. - WP_CLI::error( $e ); + $command->invoke( $final_args, $assoc_args, $extra_args ); + } catch ( WP_CLI\Iterators\Exception $e ) { + WP_CLI::error( $e->getMessage() ); } } @@ -708,10 +371,10 @@ public function show_synopsis_if_composite_command() { } } - private function run_command_and_exit( $help_exit_warning = '' ): void { + private function _run_command_and_exit( $help_exit_warning = '' ) { $this->show_synopsis_if_composite_command(); $this->run_command( $this->arguments, $this->assoc_args ); - if ( $this->cmd_starts_with( [ 'help' ] ) ) { + if ( $this->cmd_starts_with( array( 'help' ) ) ) { // Help couldn't find the command so exit with suggestion. $suggestion_or_disabled = $this->find_command_to_run( array_slice( $this->arguments, 1 ) ); if ( is_string( $suggestion_or_disabled ) ) { @@ -727,89 +390,59 @@ private function run_command_and_exit( $help_exit_warning = '' ): void { /** * Perform a command against a remote server over SSH (or a container using - * scheme of "docker", "docker-compose", or "docker-compose-run"). + * scheme of "docker" or "docker-compose"). * * @param string $connection_string Passed connection string. + * @return void */ - private function run_ssh_command( string $connection_string ): void { + private function run_ssh_command( $connection_string ) { WP_CLI::do_hook( 'before_ssh' ); $bits = Utils\parse_ssh_url( $connection_string ); - $pre_cmd = Utils\get_env_or_config( 'WP_CLI_SSH_PRE_CMD' ); + $pre_cmd = getenv( 'WP_CLI_SSH_PRE_CMD' ); if ( $pre_cmd ) { - WP_CLI::warning( "WP_CLI_SSH_PRE_CMD found, executing the following command(s) on the remote machine:\n $pre_cmd" ); - $pre_cmd = rtrim( $pre_cmd, ';' ) . '; '; } + if ( ! empty( $bits['path'] ) ) { + $pre_cmd .= 'cd ' . escapeshellarg( $bits['path'] ) . '; '; + } $env_vars = ''; if ( getenv( 'WP_CLI_STRICT_ARGS_MODE' ) ) { $env_vars .= 'WP_CLI_STRICT_ARGS_MODE=1 '; } - $wp_binary = Utils\get_env_or_config( 'WP_CLI_SSH_BINARY' ) ?: 'wp'; - $wp_args = array_slice( (array) $GLOBALS['argv'], 1 ); + $wp_binary = 'wp'; + $wp_args = array_slice( $GLOBALS['argv'], 1 ); - if ( $this->alias && ! empty( $wp_args[0] ) && ( '@' . $this->alias === $wp_args[0] || "--alias={$this->alias}" === $wp_args[0] ) ) { + if ( $this->alias && ! empty( $wp_args[0] ) && $this->alias === $wp_args[0] ) { array_shift( $wp_args ); - $runtime_alias = []; - $alias_config = $this->aliases[ $this->alias ]; - if ( is_array( $alias_config ) ) { - foreach ( $alias_config as $key => $value ) { - // Skip connection-specific keys as they are not relevant to the remote WP-CLI instance. - if ( in_array( $key, [ 'ssh', 'http', 'proxyjump', 'key', 'ssh_config' ], true ) ) { - continue; - } - $runtime_alias[ $key ] = $value; + $runtime_alias = array(); + foreach ( $this->aliases[ $this->alias ] as $key => $value ) { + if ( 'ssh' === $key ) { + continue; } + $runtime_alias[ $key ] = $value; } if ( ! empty( $runtime_alias ) ) { $encoded_alias = json_encode( - [ - '@' . $this->alias => $runtime_alias, - ] + array( + $this->alias => $runtime_alias, + ) ); - if ( false !== $encoded_alias ) { - $wp_binary = "env WP_CLI_RUNTIME_ALIAS='" . str_replace( "'", "'\\''", $encoded_alias ) . "' {$wp_binary} @{$this->alias}"; - } + $wp_binary = "WP_CLI_RUNTIME_ALIAS='{$encoded_alias}' {$wp_binary} {$this->alias}"; } } - $alias_regex = '#' . Configurator::ALIAS_REGEX . '#'; - /** @var string $v */ foreach ( $wp_args as $k => $v ) { - if ( preg_match( '#^--ssh(?:-args)?(?:=|$)|--alias=#', $v ) || preg_match( $alias_regex, $v ) ) { + if ( preg_match( '#--ssh=#', $v ) ) { unset( $wp_args[ $k ] ); } } - // Build command with minimal quoting to improve readability in debug output. - // Arguments are only quoted if they contain characters outside the safe set. - // This avoids double-escaping appearance while maintaining security: - // 1. Here: Quote args with special chars for the remote shell - // 2. generate_ssh_command(): Wrap entire command for local shell - // - // Safe characters: alphanumeric, hyphen, underscore, equals, dot, forward slash, colon - // - Hyphens (including at start like --debug) are safe because they're part of the - // wp-cli command string that's passed to the remote shell, not SSH options - // - Forward slash and colon are included because they're common in paths and URLs - // (e.g., --url=https://example.com/path) and are not shell metacharacters - // - All other characters (spaces, quotes, $, &, |, etc.) trigger quoting via escapeshellarg() - $escaped_args = []; - /** @var string $arg */ - foreach ( $wp_args as $arg ) { - // Quote empty strings and arguments with any characters outside the safe set. - // The empty string check is explicit for clarity, though regex would also catch it. - if ( '' !== $arg && preg_match( '/^[a-zA-Z0-9_=.\/:-]+$/', $arg ) ) { - $escaped_args[] = $arg; - } else { - $escaped_args[] = escapeshellarg( $arg ); - } - } - $wp_command = $pre_cmd . $env_vars . $wp_binary . ' ' . implode( ' ', $escaped_args ); - + $wp_command = $pre_cmd . $env_vars . $wp_binary . ' ' . implode( ' ', array_map( 'escapeshellarg', $wp_args ) ); $escaped_command = $this->generate_ssh_command( $bits, $wp_command ); passthru( $escaped_command, $exit_code ); @@ -823,21 +456,15 @@ private function run_ssh_command( string $connection_string ): void { /** * Generate a shell command from the parsed connection string. * - * @param array{scheme?: string, user?: string, host?: string, port?: string, path?: string} $bits Parsed connection string. + * @param array $bits Parsed connection string. * @param string $wp_command WP-CLI command to run. * @return string */ private function generate_ssh_command( $bits, $wp_command ) { $escaped_command = ''; - // Get additional SSH arguments if provided. - $ssh_args_config = WP_CLI::get_config( 'ssh-args' ); - $ssh_args = is_array( $ssh_args_config ) && ! empty( $ssh_args_config ) - ? implode( ' ', array_map( 'escapeshellarg', $ssh_args_config ) ) - : ''; - // Set default values. - foreach ( [ 'scheme', 'user', 'host', 'port', 'path', 'key', 'proxyjump', 'ssh_config' ] as $bit ) { + foreach ( array( 'scheme', 'user', 'host', 'port', 'path' ) as $bit ) { if ( ! isset( $bits[ $bit ] ) ) { $bits[ $bit ] = null; } @@ -845,153 +472,56 @@ private function generate_ssh_command( $bits, $wp_command ) { WP_CLI::debug( 'SSH ' . $bit . ': ' . $bits[ $bit ], 'bootstrap' ); } - /** - * @var array{scheme: string|null, user: string|null, host: string, port: string|null, path: string|null, key: string|null, proxyjump: string|null, ssh_config: string|null} $bits - */ - - /* - * posix_isatty(STDIN) is generally true unless something was passed on stdin - * If autodetection leads to false (fd on stdin), then `-i` is passed to `docker` cmd - * (unless WP_CLI_DOCKER_NO_INTERACTIVE is set) - */ - $is_stdout_tty = function_exists( 'posix_isatty' ) && posix_isatty( STDOUT ); - $is_stdin_tty = function_exists( 'posix_isatty' ) ? posix_isatty( STDIN ) : true; - - $docker_compose_v2_version_cmd = Utils\esc_cmd( Utils\force_env_on_nix_systems( 'docker' ) . ' compose %s', 'version' ); - $docker_compose_cmd = ! empty( Process::create( $docker_compose_v2_version_cmd )->run()->stdout ) - ? 'docker compose' - : 'docker-compose'; + $is_tty = function_exists( 'posix_isatty' ) && posix_isatty( STDOUT ); if ( 'docker' === $bits['scheme'] ) { - $command = 'docker exec %s%s%s%s%s%s sh -c %s'; + $command = 'docker exec %s%s%s sh -c %s'; $escaped_command = sprintf( $command, - $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', - $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', - $is_stdout_tty && ! Utils\get_env_or_config( 'WP_CLI_DOCKER_NO_TTY' ) ? '-t ' : '', - $is_stdin_tty || Utils\get_env_or_config( 'WP_CLI_DOCKER_NO_INTERACTIVE' ) ? '' : '-i ', + $is_tty ? '-t ' : '', escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) ); } if ( 'docker-compose' === $bits['scheme'] ) { - $command = '%s exec %s%s%s%s%s sh -c %s'; + $command = 'docker-compose exec %s%s%s sh -c %s'; $escaped_command = sprintf( $command, - $docker_compose_cmd, - $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', - $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', - $is_stdout_tty || Utils\get_env_or_config( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', + $is_tty ? '' : '-T ', escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) ); } - if ( 'docker-compose-run' === $bits['scheme'] ) { - $command = '%s run %s%s%s%s%s%s %s'; + // Vagrant ssh-config. + if ( 'vagrant' === $bits['scheme'] ) { + $command = 'vagrant ssh -c %s %s'; $escaped_command = sprintf( $command, - $docker_compose_cmd, - $ssh_args ? $ssh_args . ' ' : '', - $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', - $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', - $is_stdout_tty || Utils\get_env_or_config( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', - $is_stdin_tty || Utils\get_env_or_config( 'WP_CLI_DOCKER_NO_INTERACTIVE' ) ? '' : '-i ', - escapeshellarg( $bits['host'] ), - $wp_command + escapeshellarg( $wp_command ), + escapeshellarg( $bits['host'] ) ); } - // For "vagrant" & "ssh" schemes which don't provide a working-directory option, use `cd` - if ( $bits['path'] && in_array( $bits['scheme'], [ 'vagrant', 'ssh', null ], true ) ) { - $wp_command = 'cd ' . Utils\escapeshellarg_preserve_tilde( $bits['path'] ) . '; ' . $wp_command; - } - - // Vagrant ssh-config. - $is_vagrant_ssh = false; - if ( 'vagrant' === $bits['scheme'] ) { - $cache = WP_CLI::get_cache(); - $cache_key = 'vagrant:' . $this->project_config_path; - if ( $cache->has( $cache_key ) ) { - $cached = (string) $cache->read( $cache_key ); - $values = json_decode( $cached, true ); - } else { - $ssh_config = (string) shell_exec( 'vagrant ssh-config 2>/dev/null' ); - if ( preg_match_all( '#\s*(?[a-zA-Z]+)\s(?.+)\s*#', $ssh_config, $matches ) ) { - $values = array_combine( $matches['NAME'], $matches['VALUE'] ); - $cache->write( $cache_key, (string) json_encode( $values ) ); - } - } - - /** - * @var array{HostName?: string, Port?: int, User?: string, IdentityFile?: string} $values - */ - - if ( empty( $bits['host'] ) || ( isset( $values['Host'] ) && $bits['host'] === $values['Host'] ) ) { - $bits['scheme'] = 'ssh'; - $bits['host'] = isset( $values['HostName'] ) ? $values['HostName'] : ''; - $bits['port'] = isset( $values['Port'] ) ? $values['Port'] : ''; - $bits['user'] = isset( $values['User'] ) ? $values['User'] : ''; - $bits['key'] = isset( $values['IdentityFile'] ) ? $values['IdentityFile'] : ''; - $is_vagrant_ssh = true; - } - - // If we could not resolve the bits still, fallback to just `vagrant ssh` - if ( 'vagrant' === $bits['scheme'] ) { - $command = 'vagrant ssh' . ( $ssh_args ? ' ' . $ssh_args : '' ) . ' -c %s %s'; - - $escaped_command = sprintf( - $command, - escapeshellarg( $wp_command ), - escapeshellarg( $bits['host'] ) - ); - } - } - // Default scheme is SSH. if ( 'ssh' === $bits['scheme'] || null === $bits['scheme'] ) { - $command = 'ssh %s%s %s %s'; + $command = 'ssh -q %s%s %s %s'; if ( $bits['user'] ) { $bits['host'] = $bits['user'] . '@' . $bits['host']; } - if ( ! empty( $this->alias ) ) { - $alias_config = isset( $this->aliases[ $this->alias ] ) ? $this->aliases[ $this->alias ] : false; - - if ( is_array( $alias_config ) ) { - $bits['proxyjump'] = isset( $alias_config['proxyjump'] ) ? $alias_config['proxyjump'] : ''; - $bits['key'] = isset( $alias_config['key'] ) ? $alias_config['key'] : ''; - $bits['ssh_config'] = isset( $alias_config['ssh_config'] ) ? $alias_config['ssh_config'] : ''; - } - } - - $command_args = [ - // @phpstan-ignore cast.string - $bits['ssh_config'] ? sprintf( '-F %s', escapeshellarg( (string) $bits['ssh_config'] ) ) : '', - // @phpstan-ignore cast.string - $bits['proxyjump'] ? sprintf( '-J %s', escapeshellarg( (string) $bits['proxyjump'] ) ) : '', - $bits['port'] ? sprintf( '-p %d', (int) $bits['port'] ) : '', - // @phpstan-ignore cast.string - $bits['key'] ? sprintf( '-i %s', escapeshellarg( (string) $bits['key'] ) ) : '', - $is_vagrant_ssh ? '-o StrictHostKeyChecking=no' : '', - $is_vagrant_ssh ? '-o UserKnownHostsFile=/dev/null' : '', - $is_vagrant_ssh ? '-o BatchMode=yes' : '', - $is_stdout_tty ? '-t' : '-T', - WP_CLI::get_config( 'debug' ) ? '-vvv' : '-q', - ]; - $escaped_command = sprintf( $command, - $ssh_args ? $ssh_args . ' ' : '', - implode( ' ', array_filter( $command_args ) ), + $bits['port'] ? '-p ' . (int) $bits['port'] . ' ' : '', escapeshellarg( $bits['host'] ), + $is_tty ? '-t' : '-T', escapeshellarg( $wp_command ) ); } @@ -1002,76 +532,45 @@ private function generate_ssh_command( $bits, $wp_command ) { } /** - * Check whether a given command is disabled by the config. + * Check whether a given command is disabled by the config * * @return bool */ public function is_command_disabled( $command ) { - return false !== $this->get_command_disabled_reason( $command ); + $path = implode( ' ', array_slice( \WP_CLI\Dispatcher\get_path( $command ), 1 ) ); + return in_array( $path, $this->config['disabled_commands'] ); } /** - * Get the reason why a command is disabled, or false if it isn't. - * - * @return string|false Reason string, or false if the command is not disabled. - */ - public function get_command_disabled_reason( $command ) { - if ( $command instanceof Dispatcher\DisabledCommand ) { - return $command->get_disabled_reason(); - } - - $path = implode( ' ', array_slice( Dispatcher\get_path( $command ), 1 ) ); - /** - * @var string[] $disabled_commands - */ - $disabled_commands = $this->config['disabled_commands']; - if ( in_array( $path, $disabled_commands, true ) ) { - return 'Disabled via configuration file'; - } - return false; - } - - /** - * Returns wp-config.php code, skipping the loading of wp-settings.php. - * - * @param string $wp_config_path Optional. Config file path. If left empty, it tries to - * locate the wp-config.php file automatically. + * Returns wp-config.php code, skipping the loading of wp-settings.php * * @return string */ - public function get_wp_config_code( $wp_config_path = '' ) { - if ( empty( $wp_config_path ) ) { - $wp_config_path = Utils\locate_wp_config(); - } - - $wp_config_code = (string) file_get_contents( $wp_config_path ); + public function get_wp_config_code() { + $wp_config_path = Utils\locate_wp_config(); - // Detect and strip byte-order marks (BOMs). - // This code assumes they can only be found on the first line. - foreach ( self::BYTE_ORDER_MARKS as $bom_name => $bom_sequence ) { - WP_CLI::debug( "Looking for {$bom_name} BOM", 'bootstrap' ); + $wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) ); - $length = strlen( $bom_sequence ); + $found_wp_settings = false; - while ( substr( $wp_config_code, 0, $length ) === $bom_sequence ) { - WP_CLI::warning( - "{$bom_name} byte-order mark (BOM) detected in wp-config.php file, stripping it for parsing." - ); + $lines_to_run = array(); - $wp_config_code = substr( $wp_config_code, $length ); + foreach ( $wp_config_code as $line ) { + if ( preg_match( '/^\s*require.+wp-settings\.php/', $line ) ) { + $found_wp_settings = true; + continue; } - } - - $count = 0; - $wp_config_code = (string) preg_replace( '/\s*require(?:_once)?\s*.*wp-settings\.php.*\s*;/', '', $wp_config_code, -1, $count ); + $lines_to_run[] = $line; + } - if ( 0 === $count ) { + if ( ! $found_wp_settings ) { WP_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' ); } - $source = Path::replace_path_consts( $wp_config_code, $wp_config_path ); - return (string) preg_replace( '|^\s*\<\?php\s*|', '', $source ); + $source = implode( "\n", $lines_to_run ); + $source = Utils\replace_path_consts( $source, $wp_config_path ); + return preg_replace( '|^\s*\<\?php\s*|', '', $source ); } /** @@ -1082,34 +581,13 @@ public function get_wp_config_code( $wp_config_path = '' ) { * @return array */ private static function back_compat_conversions( $args, $assoc_args ) { - // On Windows (PowerShell), command substitution like $(wp post list --format=ids) - // returns space-separated values as a single string argument instead of separate arguments. - // Split such arguments to maintain compatibility with Unix-like behavior. - if ( Utils\is_windows() ) { - $split_args = []; - foreach ( $args as $arg ) { - // Check if the argument contains space-separated numeric IDs - // We only split if the entire argument matches the pattern of space-separated numbers - if ( is_string( $arg ) && preg_match( '/^\d+(\s+\d+)+$/', $arg ) ) { - // Split on whitespace and add each ID as a separate argument - $ids = preg_split( '/\s+/', $arg, -1, PREG_SPLIT_NO_EMPTY ); - if ( false !== $ids ) { - array_push( $split_args, ...$ids ); - } - } else { - $split_args[] = $arg; - } - } - $args = $split_args; - } - - $top_level_aliases = [ - 'sql' => 'db', + $top_level_aliases = array( + 'sql' => 'db', 'blog' => 'site', - ]; + ); if ( count( $args ) > 0 ) { foreach ( $top_level_aliases as $old => $new ) { - if ( $old === $args[0] ) { + if ( $old == $args[0] ) { $args[0] = $new; break; } @@ -1117,40 +595,30 @@ private static function back_compat_conversions( $args, $assoc_args ) { } // *-meta -> * meta - if ( ! empty( $args ) && preg_match( '/(post|comment|user|network)-meta/', (string) $args[0], $matches ) ) { + if ( ! empty( $args ) && preg_match( '/(post|comment|user|network)-meta/', $args[0], $matches ) ) { array_shift( $args ); array_unshift( $args, 'meta' ); array_unshift( $args, $matches[1] ); } - // cli aliases -> cli alias list - if ( [ 'cli', 'aliases' ] === array_slice( $args, 0, 2 ) ) { - list( $args[0], $args[1], $args[2] ) = [ 'cli', 'alias', 'list' ]; - } - // core (multsite-)install --admin_name= -> --admin_user= - if ( count( $args ) > 0 && 'core' === $args[0] && isset( $assoc_args['admin_name'] ) ) { + if ( count( $args ) > 0 && 'core' == $args[0] && isset( $assoc_args['admin_name'] ) ) { $assoc_args['admin_user'] = $assoc_args['admin_name']; unset( $assoc_args['admin_name'] ); } // core config -> config create - if ( [ 'core', 'config' ] === array_slice( $args, 0, 2 ) ) { - list( $args[0], $args[1] ) = [ 'config', 'create' ]; + if ( array( 'core', 'config' ) == array_slice( $args, 0, 2 ) ) { + list( $args[0], $args[1] ) = array( 'config', 'create' ); } // core language -> language core - if ( [ 'core', 'language' ] === array_slice( $args, 0, 2 ) ) { - list( $args[0], $args[1] ) = [ 'language', 'core' ]; - } - - // checksum core -> core verify-checksums - if ( [ 'checksum', 'core' ] === array_slice( $args, 0, 2 ) ) { - list( $args[0], $args[1] ) = [ 'core', 'verify-checksums' ]; + if ( array( 'core', 'language' ) == array_slice( $args, 0, 2 ) ) { + list( $args[0], $args[1] ) = array( 'language', 'core' ); } - // checksum plugin -> plugin verify-checksums - if ( [ 'checksum', 'plugin' ] === array_slice( $args, 0, 2 ) ) { - list( $args[0], $args[1] ) = [ 'plugin', 'verify-checksums' ]; + // core verify-checksums -> checksum core + if ( array( 'core', 'verify-checksums' ) == array_slice( $args, 0, 2 ) ) { + list( $args[0], $args[1] ) = array( 'checksum', 'core' ); } // site create --site_id= -> site create --network_id= @@ -1160,28 +628,28 @@ private static function back_compat_conversions( $args, $assoc_args ) { } // {plugin|theme} update-all -> {plugin|theme} update --all - if ( count( $args ) > 1 && in_array( $args[0], [ 'plugin', 'theme' ], true ) + if ( count( $args ) > 1 && in_array( $args[0], array( 'plugin', 'theme' ) ) && 'update-all' === $args[1] ) { - $args[1] = 'update'; + $args[1] = 'update'; $assoc_args['all'] = true; } // transient delete-expired -> transient delete --expired if ( count( $args ) > 1 && 'transient' === $args[0] && 'delete-expired' === $args[1] ) { - $args[1] = 'delete'; + $args[1] = 'delete'; $assoc_args['expired'] = true; } // transient delete-all -> transient delete --all if ( count( $args ) > 1 && 'transient' === $args[0] && 'delete-all' === $args[1] ) { - $args[1] = 'delete'; + $args[1] = 'delete'; $assoc_args['all'] = true; } // plugin scaffold -> scaffold plugin - if ( [ 'plugin', 'scaffold' ] === array_slice( $args, 0, 2 ) ) { - list( $args[0], $args[1] ) = [ $args[1], $args[0] ]; + if ( array( 'plugin', 'scaffold' ) == array_slice( $args, 0, 2 ) ) { + list( $args[0], $args[1] ) = array( $args[1], $args[0] ); } // foo --help -> help foo @@ -1191,7 +659,7 @@ private static function back_compat_conversions( $args, $assoc_args ) { } // {post|user} list --ids -> {post|user} list --format=ids - if ( count( $args ) > 1 && in_array( $args[0], [ 'post', 'user' ], true ) + if ( count( $args ) > 1 && in_array( $args[0], array( 'post', 'user' ) ) && 'list' === $args[1] && isset( $assoc_args['ids'] ) ) { @@ -1207,10 +675,10 @@ private static function back_compat_conversions( $args, $assoc_args ) { // --{version|info} -> cli {version|info} if ( empty( $args ) ) { - $special_flags = [ 'version', 'info' ]; + $special_flags = array( 'version', 'info' ); foreach ( $special_flags as $key ) { if ( isset( $assoc_args[ $key ] ) ) { - $args = [ 'cli', $key ]; + $args = array( 'cli', $key ); unset( $assoc_args[ $key ] ); break; } @@ -1218,67 +686,44 @@ private static function back_compat_conversions( $args, $assoc_args ) { } // (post|comment|site|term) url --> (post|comment|site|term) list --*__in --field=url - if ( count( $args ) >= 2 && in_array( $args[0], [ 'post', 'comment', 'site', 'term' ], true ) && 'url' === $args[1] ) { + if ( count( $args ) >= 2 && in_array( $args[0], array( 'post', 'comment', 'site', 'term' ) ) && 'url' === $args[1] ) { switch ( $args[0] ) { case 'post': - $post_ids = array_slice( $args, 2 ); - $args = [ 'post', 'list' ]; - $assoc_args['post__in'] = implode( ',', $post_ids ); + $post_ids = array_slice( $args, 2 ); + $args = array( 'post', 'list' ); + $assoc_args['post__in'] = implode( ',', $post_ids ); $assoc_args['post_type'] = 'any'; - $assoc_args['orderby'] = 'post__in'; - $assoc_args['field'] = 'url'; + $assoc_args['orderby'] = 'post__in'; + $assoc_args['field'] = 'url'; break; case 'comment': - $comment_ids = array_slice( $args, 2 ); - $args = [ 'comment', 'list' ]; + $comment_ids = array_slice( $args, 2 ); + $args = array( 'comment', 'list' ); $assoc_args['comment__in'] = implode( ',', $comment_ids ); - $assoc_args['orderby'] = 'comment__in'; - $assoc_args['field'] = 'url'; + $assoc_args['orderby'] = 'comment__in'; + $assoc_args['field'] = 'url'; break; case 'site': - $site_ids = array_slice( $args, 2 ); - $args = [ 'site', 'list' ]; + $site_ids = array_slice( $args, 2 ); + $args = array( 'site', 'list' ); $assoc_args['site__in'] = implode( ',', $site_ids ); - $assoc_args['field'] = 'url'; + $assoc_args['field'] = 'url'; break; case 'term': $taxonomy = ''; if ( isset( $args[2] ) ) { $taxonomy = $args[2]; } - $term_ids = array_slice( $args, 3 ); - $args = [ 'term', 'list', $taxonomy ]; + $term_ids = array_slice( $args, 3 ); + $args = array( 'term', 'list', $taxonomy ); $assoc_args['include'] = implode( ',', $term_ids ); $assoc_args['orderby'] = 'include'; - $assoc_args['field'] = 'url'; + $assoc_args['field'] = 'url'; break; } } - // config get --[global|constant]= --> config get --type=constant|variable - // config get --> config list - if ( count( $args ) === 2 - && 'config' === $args[0] - && 'get' === $args[1] ) { - if ( isset( $assoc_args['global'] ) ) { - $name = $assoc_args['global']; - $type = 'variable'; - unset( $assoc_args['global'] ); - } elseif ( isset( $assoc_args['constant'] ) ) { - $name = $assoc_args['constant']; - $type = 'constant'; - unset( $assoc_args['constant'] ); - } - if ( ! empty( $name ) && ! empty( $type ) ) { - $args[] = $name; - $assoc_args['type'] = $type; - } else { - // We had a 'config get' without a '', so assume 'list' was wanted. - $args[1] = 'list'; - } - } - - return [ $args, $assoc_args ]; + return array( $args, $assoc_args ); } /** @@ -1292,64 +737,49 @@ public function in_color() { public function init_colorization() { if ( 'auto' === $this->config['color'] ) { - $this->colorize = ( ! Utils\isPiped() && ! Utils\is_windows() ); + $this->colorize = ( ! \WP_CLI\Utils\isPiped() && ! \WP_CLI\Utils\is_windows() ); } else { - // @phpstan-ignore assign.propertyType $this->colorize = $this->config['color']; } } public function init_logger() { if ( $this->config['quiet'] ) { - $logger = new Loggers\Quiet( $this->in_color() ); + $logger = new \WP_CLI\Loggers\Quiet; } else { - $logger = new Loggers\Regular( $this->in_color() ); + $logger = new \WP_CLI\Loggers\Regular( $this->in_color() ); } WP_CLI::set_logger( $logger ); } public function get_required_files() { - return $this->required_files; + return $this->_required_files; } /** * Do WordPress core files exist? + * + * @return bool */ - private function wp_exists(): bool { - return file_exists( ABSPATH . 'wp-includes/version.php' ); - } - - /** - * Are WordPress core files readable? - */ - private function wp_is_readable(): bool { + private function wp_exists() { return is_readable( ABSPATH . 'wp-includes/version.php' ); } - private function check_wp_version(): void { - $wp_exists = $this->wp_exists(); - $wp_is_readable = $this->wp_is_readable(); - if ( ! $wp_exists || ! $wp_is_readable ) { + private function check_wp_version() { + if ( ! $this->wp_exists() ) { $this->show_synopsis_if_composite_command(); - $is_help = $this->cmd_starts_with( [ 'help' ] ); - $args = $is_help ? array_slice( $this->arguments, 1 ) : $this->arguments; - $suggestion_or_disabled = $this->find_command_to_run( $args, Utils\get_env_or_config( 'WP_CLI_AUTOCORRECT' ) ? 'auto' : 'confirm' ); + // If the command doesn't exist use as error. + $args = $this->cmd_starts_with( array( 'help' ) ) ? array_slice( $this->arguments, 1 ) : $this->arguments; + $suggestion_or_disabled = $this->find_command_to_run( $args ); if ( is_string( $suggestion_or_disabled ) ) { if ( ! preg_match( '/disabled from the config file.$/', $suggestion_or_disabled ) ) { - WP_CLI::warning( "No WordPress installation found. If the command '" . implode( ' ', $args ) . "' is in a plugin or theme, pass --path=`path/to/wordpress`." ); + WP_CLI::warning( "No WordPress install found. If the command '" . implode( ' ', $args ) . "' is in a plugin or theme, pass --path=`path/to/wordpress`." ); } WP_CLI::error( $suggestion_or_disabled ); } - - if ( $wp_exists && ! $wp_is_readable ) { - WP_CLI::error( - 'It seems, the WordPress core files do not have the proper file permissions.' - ); - } WP_CLI::error( - "This does not seem to be a WordPress installation.\n" . - 'The used path is: ' . ABSPATH . "\n" . + "This does not seem to be a WordPress install.\n" . 'Pass --path=`path/to/wordpress` or run `wp core download`.' ); } @@ -1359,6 +789,7 @@ private function check_wp_version(): void { $minimum_version = '3.7'; + // @codingStandardsIgnoreStart if ( version_compare( $wp_version, $minimum_version, '<' ) ) { WP_CLI::error( "WP-CLI needs WordPress $minimum_version or later to work properly. " . @@ -1366,38 +797,30 @@ private function check_wp_version(): void { 'Try running `wp core download --force`.' ); } + // @codingStandardsIgnoreEnd } public function init_config() { - $configurator = WP_CLI::get_configurator(); + $configurator = \WP_CLI::get_configurator(); - /** - * @var string[] $argv - */ - $argv = array_slice( (array) $GLOBALS['argv'], 1 ); + $argv = array_slice( $GLOBALS['argv'], 1 ); - // Check if we use an alias with @foo syntax (must be done before parsing args) $this->alias = null; if ( ! empty( $argv[0] ) && preg_match( '#' . Configurator::ALIAS_REGEX . '#', $argv[0], $matches ) ) { - $this->alias = substr( array_shift( $argv ), 1 ); // Remove the @ prefix and shift from argv + $this->alias = array_shift( $argv ); } // File config { - $this->system_config_path = $this->get_system_config_path(); - $this->global_config_path = $this->get_global_config_path(); + $this->global_config_path = $this->get_global_config_path(); $this->project_config_path = $this->get_project_config_path(); - $configurator->merge_yml( (string) $this->system_config_path, $this->alias ); - $config = $configurator->to_array(); - $this->required_files['system'] = $config[0]['require']; - $configurator->merge_yml( (string) $this->global_config_path, $this->alias ); - $config = $configurator->to_array(); - $this->required_files['global'] = isset( $config[0]['require'] ) ? (array) $config[0]['require'] : []; - $configurator->merge_yml( (string) $this->project_config_path, $this->alias ); - $config = $configurator->to_array(); - $this->required_files['project'] = isset( $config[0]['require'] ) ? (array) $config[0]['require'] : []; - $this->required_files['runtime'] = []; + $configurator->merge_yml( $this->global_config_path, $this->alias ); + $config = $configurator->to_array(); + $this->_required_files['global'] = $config[0]['require']; + $configurator->merge_yml( $this->project_config_path, $this->alias ); + $config = $configurator->to_array(); + $this->_required_files['project'] = $config[0]['require']; } // Runtime config and args @@ -1405,217 +828,119 @@ public function init_config() { list( $args, $assoc_args, $this->runtime_config ) = $configurator->parse_args( $argv ); list( $this->arguments, $this->assoc_args ) = self::back_compat_conversions( - $args, - $assoc_args + $args, $assoc_args ); - $configurator->merge_array( (array) $this->runtime_config ); - } - - // Check if --alias flag was used (takes precedence over @foo if both provided) - if ( ! empty( $this->runtime_config['alias'] ) ) { - /** - * @var string $runtime_alias - */ - $runtime_alias = $this->runtime_config['alias']; - $this->alias = $runtime_alias; + $configurator->merge_array( $this->runtime_config ); } list( $this->config, $this->extra_config ) = $configurator->to_array(); - $this->aliases = $configurator->get_aliases(); - $this->raw_aliases = $configurator->get_raw_aliases(); - $this->add_at_all_alias( $this->aliases ); - $this->add_at_all_alias( $this->raw_aliases ); - $this->required_files['runtime'] = $this->config['require']; + $this->aliases = $configurator->get_aliases(); + if ( count( $this->aliases ) && ! isset( $this->aliases['@all'] ) ) { + $this->aliases = array_reverse( $this->aliases ); + $this->aliases['@all'] = 'Run command against every registered alias.'; + $this->aliases = array_reverse( $this->aliases ); + } + $this->_required_files['runtime'] = $this->config['require']; } - /** - * Add the @all alias to an aliases array if it doesn't already exist. - * - * @param array $aliases Aliases array passed by reference. - */ - private function add_at_all_alias( &$aliases ) { - if ( count( $aliases ) && ! isset( $aliases['all'] ) ) { - $aliases = array_reverse( $aliases ); - $aliases['all'] = 'Run command against every registered alias.'; - $aliases = array_reverse( $aliases ); - } + private function check_root() { + if ( $this->config['allow-root'] ) { + return; # they're aware of the risks! + } + if ( count( $this->arguments ) >= 2 && 'cli' === $this->arguments[0] && in_array( $this->arguments[1], array( 'update', 'info' ), true ) ) { + return; # make it easier to update root-owned copies + } + if ( ! function_exists( 'posix_geteuid' ) ) { + return; # posix functions not available + } + if ( posix_geteuid() !== 0 ) { + return; # not root + } + + WP_CLI::error( + "YIKES! It looks like you're running this as root. You probably meant to " . + "run this as the user that your WordPress install exists under.\n" . + "\n" . + "If you REALLY mean to run this as root, we won't stop you, but just " . + 'bear in mind that any code on this site will then have full control of ' . + "your server, making it quite DANGEROUS.\n" . + "\n" . + "If you'd like to continue as root, please run this again, adding this " . + "flag: --allow-root\n" . + "\n" . + "If you'd like to run it as the user that this site is under, you can " . + "run the following to become the respective user:\n" . + "\n" . + " sudo -u USER -i -- wp \n" . + "\n" + ); } - private function run_alias_group( $aliases ): void { + private function run_alias_group( $aliases ) { Utils\check_proc_available( 'group alias' ); $php_bin = escapeshellarg( Utils\get_php_binary() ); - /** - * @var string[] $argv - */ - $argv = $GLOBALS['argv']; - - $script_path = escapeshellarg( $argv[0] ); - - $wp_cli_config_path = (string) getenv( 'WP_CLI_CONFIG_PATH' ); + $script_path = $GLOBALS['argv'][0]; - if ( $wp_cli_config_path ) { - $config_path = $wp_cli_config_path; + if ( getenv( 'WP_CLI_CONFIG_PATH' ) ) { + $config_path = getenv( 'WP_CLI_CONFIG_PATH' ); } else { - $config_path = Path::get_home_dir() . '/.wp-cli/config.yml'; - } - - // Exclude 'quiet' from runtime config for subprocesses to allow command output. - $subprocess_runtime_config = $this->runtime_config; - unset( $subprocess_runtime_config['quiet'] ); - - // Precompute command components that are the same for all aliases. - $alias_regex = '#' . Configurator::ALIAS_REGEX . '#'; - $args = implode( - ' ', - array_map( - 'escapeshellarg', - array_filter( - (array) $this->arguments, - function ( $value ) use ( $alias_regex ) { - return ! preg_match( $alias_regex, $value ); - } - ) - ) - ); - - // Filter out --ssh and --alias args from the subcommands. - $filtered_assoc_args = (array) $this->assoc_args; - unset( $filtered_assoc_args['ssh'], $filtered_assoc_args['alias'] ); - - $assoc_args = Utils\assoc_args_to_str( $filtered_assoc_args ); - - $filtered_runtime_config = (array) $subprocess_runtime_config; - unset( $filtered_runtime_config['alias'] ); - $runtime_config = Utils\assoc_args_to_str( $filtered_runtime_config ); - - // Check if parallel execution is enabled via environment variable. - $parallel = (bool) Utils\get_env_or_config( 'WP_CLI_ALIAS_GROUPS_PARALLEL' ); - - // Read STDIN once upfront so every subprocess in the group receives the - // same input. When STDIN is a pipe (e.g. `cat file.php | wp @group eval-file -`) - // only the first subprocess would otherwise consume the stream; subsequent - // ones would see an immediate EOF. - $stdin_stream = null; - if ( Utils\has_stdin() ) { - // Spool STDIN into a temporary, rewindable stream so it can be - // replayed to each subprocess without holding it all in memory. - $stdin_stream = fopen( 'php://temp/maxmemory:5242880', 'w+' ); // 5MB in-memory, then disk. - if ( false === $stdin_stream ) { - $stdin_stream = null; - } else { - $result = stream_copy_to_stream( STDIN, $stdin_stream ); - if ( false === $result ) { - fclose( $stdin_stream ); - $stdin_stream = null; - } else { - rewind( $stdin_stream ); - } - } + $config_path = Utils\get_home_dir() . '/.wp-cli/config.yml'; } + $config_path = escapeshellarg( $config_path ); - if ( $parallel ) { - // Run aliases in parallel. - // Note: Output from multiple processes will be interleaved and non-deterministic. - $procs = []; - foreach ( $aliases as $alias ) { - WP_CLI::log( '@' . $alias ); - $full_command = "{$php_bin} {$script_path} --alias=" . escapeshellarg( $alias ) . " {$args}{$assoc_args}{$runtime_config}"; - $pipes = []; - $stdin_spec = null !== $stdin_stream ? [ 'pipe', 'r' ] : STDIN; - $env = getenv(); - $env['WP_CLI_CONFIG_PATH'] = $config_path; - - fflush( STDOUT ); - fflush( STDERR ); - - $proc = Utils\proc_open_compat( $full_command, [ $stdin_spec, STDOUT, STDERR ], $pipes, null, $env ); - - if ( $proc ) { - if ( null !== $stdin_stream ) { - rewind( $stdin_stream ); - stream_copy_to_stream( $stdin_stream, $pipes[0] ); - fclose( $pipes[0] ); - } - $procs[] = $proc; - } - } - - // Wait for all processes to complete. - foreach ( $procs as $proc ) { - proc_close( $proc ); - } - } else { - // Run aliases sequentially (original behavior). - foreach ( $aliases as $alias ) { - WP_CLI::log( '@' . $alias ); - $full_command = "{$php_bin} {$script_path} --alias=" . escapeshellarg( $alias ) . " {$args}{$assoc_args}{$runtime_config}"; - $pipes = []; - $stdin_spec = null !== $stdin_stream ? [ 'pipe', 'r' ] : STDIN; - $env = getenv(); - $env['WP_CLI_CONFIG_PATH'] = $config_path; - - fflush( STDOUT ); - fflush( STDERR ); - - $proc = Utils\proc_open_compat( $full_command, [ $stdin_spec, STDOUT, STDERR ], $pipes, null, $env ); - - if ( $proc ) { - if ( null !== $stdin_stream ) { - rewind( $stdin_stream ); - stream_copy_to_stream( $stdin_stream, $pipes[0] ); - fclose( $pipes[0] ); - } - proc_close( $proc ); - } - } + foreach ( $aliases as $alias ) { + WP_CLI::log( $alias ); + $args = implode( ' ', array_map( 'escapeshellarg', $this->arguments ) ); + $assoc_args = Utils\assoc_args_to_str( $this->assoc_args ); + $runtime_config = Utils\assoc_args_to_str( $this->runtime_config ); + $full_command = "WP_CLI_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$alias} {$args}{$assoc_args}{$runtime_config}"; + $proc = Utils\proc_open_compat( $full_command, array( STDIN, STDOUT, STDERR ), $pipes ); + proc_close( $proc ); } } - private function set_alias( $alias ): void { + private function set_alias( $alias ) { $orig_config = $this->config; - /** @var array $alias_config */ - // @phpstan-ignore varTag.type - $alias_config = (array) $this->aliases[ $alias ]; + $alias_config = $this->aliases[ $this->alias ]; $this->config = array_merge( $orig_config, $alias_config ); foreach ( $alias_config as $key => $_ ) { - if ( isset( $orig_config[ (string) $key ] ) && ! is_null( $orig_config[ (string) $key ] ) ) { - // @phpstan-ignore assign.propertyType - $this->assoc_args[ (string) $key ] = $orig_config[ (string) $key ]; + if ( isset( $orig_config[ $key ] ) && ! is_null( $orig_config[ $key ] ) ) { + $this->assoc_args[ $key ] = $orig_config[ $key ]; } } } public function start() { + // Enable PHP error reporting to stderr if testing. Will need to be re-enabled after WP loads. if ( getenv( 'BEHAT_RUN' ) ) { $this->enable_error_reporting(); } - WP_CLI::debug( $this->system_config_path_debug, 'bootstrap' ); - WP_CLI::debug( $this->global_config_path_debug, 'bootstrap' ); - WP_CLI::debug( $this->project_config_path_debug, 'bootstrap' ); - // @phpstan-ignore argument.type - WP_CLI::debug( 'argv: ' . implode( ' ', (array) $GLOBALS['argv'] ), 'bootstrap' ); + WP_CLI::debug( $this->_global_config_path_debug, 'bootstrap' ); + WP_CLI::debug( $this->_project_config_path_debug, 'bootstrap' ); + WP_CLI::debug( 'argv: ' . implode( ' ', $GLOBALS['argv'] ), 'bootstrap' ); + $this->check_root(); if ( $this->alias ) { - if ( 'all' === $this->alias && ! isset( $this->aliases['all'] ) ) { - WP_CLI::error( "Cannot use 'all' when no aliases are registered." ); + if ( '@all' === $this->alias && ! isset( $this->aliases['@all'] ) ) { + WP_CLI::error( "Cannot use '@all' when no aliases are registered." ); } - if ( 'all' === $this->alias && is_string( $this->aliases['all'] ) ) { + if ( '@all' === $this->alias && is_string( $this->aliases['@all'] ) ) { $aliases = array_keys( $this->aliases ); - $k = array_search( 'all', $aliases, true ); + $k = array_search( '@all', $aliases ); unset( $aliases[ $k ] ); $this->run_alias_group( $aliases ); exit; } if ( ! array_key_exists( $this->alias, $this->aliases ) ) { - $error_msg = "Alias '{$this->alias}' not found."; - $suggestion = Utils\get_suggestion( (string) $this->alias, array_keys( $this->aliases ), $threshold = 2 ); + $error_msg = "Alias '{$this->alias}' not found."; + $suggestion = Utils\get_suggestion( $this->alias, array_keys( $this->aliases ), $threshold = 2 ); if ( $suggestion ) { $error_msg .= PHP_EOL . "Did you mean '{$suggestion}'?"; } @@ -1623,22 +948,10 @@ public function start() { } // Numerically indexed means a group of aliases if ( isset( $this->aliases[ $this->alias ][0] ) ) { - /** @var array $group_aliases */ - $group_aliases = (array) $this->aliases[ $this->alias ]; - $all_aliases = array_keys( $this->aliases ); - $diff = array_diff( $group_aliases, $all_aliases ); - if ( ! empty( $diff ) ) { - WP_CLI::error( - "Group '@{$this->alias}' contains one or more invalid aliases: " . implode( - ', ', - array_map( - function ( $alias ) { - return '@' . $alias; - }, - $diff - ) - ) - ); + $group_aliases = $this->aliases[ $this->alias ]; + $all_aliases = array_keys( $this->aliases ); + if ( $diff = array_diff( $group_aliases, $all_aliases ) ) { + WP_CLI::error( "Group '{$this->alias}' contains one or more invalid aliases: " . implode( ', ', $diff ) ); } $this->run_alias_group( $group_aliases ); exit; @@ -1654,7 +967,7 @@ function ( $alias ) { // Protect 'cli info' from most of the runtime, // except when the command will be run over SSH if ( 'cli' === $this->arguments[0] && ! empty( $this->arguments[1] ) && 'info' === $this->arguments[1] && ! $this->config['ssh'] ) { - $this->run_command_and_exit(); + $this->_run_command_and_exit(); } if ( isset( $this->config['http'] ) && ! class_exists( '\WP_REST_CLI\Runner' ) ) { @@ -1662,87 +975,36 @@ function ( $alias ) { } if ( $this->config['ssh'] ) { - // @phpstan-ignore cast.string - $this->run_ssh_command( (string) $this->config['ssh'] ); + $this->run_ssh_command( $this->config['ssh'] ); return; } - // Log WP-CLI HTTP requests - WP_CLI::add_hook( - 'http_request_options', - static function ( $options, $method, $url ) { - WP_CLI::debug( sprintf( 'HTTP %s request to %s', $method, $url ), 'http' ); - return $options; - } - ); - // Handle --path parameter self::set_wp_root( $this->find_wp_root() ); // First try at showing man page - if help command and either haven't found 'version.php' or 'wp-config.php' (so won't be loading WP & adding commands) or help on subcommand. - if ( $this->cmd_starts_with( [ 'help' ] ) + if ( $this->cmd_starts_with( array( 'help' ) ) && ( ! $this->wp_exists() || ! Utils\locate_wp_config() || count( $this->arguments ) > 2 ) ) { - $cmd_args = array_slice( $this->arguments, 1 ); - $r = $this->find_command_to_run( $cmd_args, 'none' ); - - if ( is_array( $r ) ) { - $this->auto_check_update(); - $this->run_command( $this->arguments, $this->assoc_args ); - } - // Help wasn't run or didn't exit, so the command wasn't resolved at this stage. + $this->auto_check_update(); + $this->run_command( $this->arguments, $this->assoc_args ); + // Help didn't exit so failed to find the command at this stage. } // Handle --url parameter $url = self::guess_url( $this->config ); if ( $url ) { - WP_CLI::set_url( $url ); - } - - // Handle --assume-https parameter - if ( ! empty( $this->config['assume-https'] ) ) { - /** - * @var array{HTTPS: string|int} $_SERVER - */ - if ( ! isset( $_SERVER['HTTPS'] ) ) { - $_SERVER['HTTPS'] = 'on'; - } else { - $https_value = strtolower( (string) $_SERVER['HTTPS'] ); - if ( 'on' !== $https_value && '1' !== $https_value ) { - $_SERVER['HTTPS'] = 'on'; - } - } + \WP_CLI::set_url( $url ); } $this->do_early_invoke( 'before_wp_load' ); - // Second try at showing man page for help commands. - if ( $this->cmd_starts_with( [ 'help' ] ) - && ( ! $this->wp_exists() - || ! Utils\locate_wp_config() - || count( $this->arguments ) > 2 - ) ) { - $cmd_args = array_slice( $this->arguments, 1 ); - $autocorrect = ( ! $this->wp_exists() || ! Utils\locate_wp_config() ) ? ( Utils\get_env_or_config( 'WP_CLI_AUTOCORRECT' ) ? 'auto' : 'confirm' ) : 'none'; - $r = $this->find_command_to_run( $cmd_args, $autocorrect ); - - if ( is_array( $r ) ) { - // `::find_command_to_run()` modifies `$this->arguments`. - // @phpstan-ignore booleanNot.alwaysFalse - if ( ! $this->cmd_starts_with( [ 'help' ] ) ) { - $this->arguments = array_merge( [ 'help' ], $this->arguments ); - } - $this->auto_check_update(); - $this->run_command( $this->arguments, $this->assoc_args ); - } - } - $this->check_wp_version(); - if ( $this->cmd_starts_with( [ 'config', 'create' ] ) ) { - $this->run_command_and_exit(); + if ( $this->cmd_starts_with( array( 'config', 'create' ) ) ) { + $this->_run_command_and_exit(); } if ( ! Utils\locate_wp_config() ) { @@ -1752,59 +1014,48 @@ static function ( $options, $method, $url ) { ); } - /* - * Set the MySQLi error reporting off because WordPress handles its own. - * This is due to the default value change from `MYSQLI_REPORT_OFF` - * to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1. - */ - if ( function_exists( 'mysqli_report' ) ) { - mysqli_report( 0 ); // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_report - } - - // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Declaring WP native constants. - - if ( $this->cmd_starts_with( [ 'core', 'is-installed' ] ) - || $this->cmd_starts_with( [ 'core', 'update-db' ] ) ) { + if ( $this->cmd_starts_with( array( 'core', 'is-installed' ) ) + || $this->cmd_starts_with( array( 'core', 'update-db' ) ) ) { define( 'WP_INSTALLING', true ); } if ( count( $this->arguments ) >= 2 && 'core' === $this->arguments[0] && - in_array( $this->arguments[1], [ 'install', 'multisite-install' ], true ) + in_array( $this->arguments[1], array( 'install', 'multisite-install' ) ) ) { define( 'WP_INSTALLING', true ); // We really need a URL here if ( ! isset( $_SERVER['HTTP_HOST'] ) ) { - $url = 'https://example.com'; - WP_CLI::set_url( $url ); + $url = 'http://example.com'; + \WP_CLI::set_url( $url ); } - if ( 'multisite-install' === $this->arguments[1] && $url ) { + if ( 'multisite-install' == $this->arguments[1] ) { // need to fake some globals to skip the checks in wp-includes/ms-settings.php $url_parts = Utils\parse_url( $url ); self::fake_current_site_blog( $url_parts ); if ( ! defined( 'COOKIEHASH' ) ) { - define( 'COOKIEHASH', md5( (string) ( $url_parts['host'] ?? '' ) ) ); + define( 'COOKIEHASH', md5( $url_parts['host'] ) ); } } } - if ( $this->cmd_starts_with( [ 'import' ] ) ) { + if ( $this->cmd_starts_with( array( 'import' ) ) ) { define( 'WP_LOAD_IMPORTERS', true ); define( 'WP_IMPORTING', true ); } - if ( $this->cmd_starts_with( [ 'cron', 'event', 'run' ] ) ) { + if ( $this->cmd_starts_with( array( 'cron', 'event', 'run' ) ) ) { define( 'DOING_CRON', true ); } - // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound $this->load_wordpress(); - $this->run_command_and_exit(); + $this->_run_command_and_exit(); + } /** @@ -1821,9 +1072,6 @@ public function load_wordpress() { $wp_cli_is_loaded = true; - // Handle --context flag. - $this->context_manager->switch_context( $this->config ); - WP_CLI::debug( 'Begin WordPress load', 'bootstrap' ); WP_CLI::do_hook( 'before_wp_load' ); @@ -1842,17 +1090,12 @@ public function load_wordpress() { // Load wp-config.php code, in the global scope $wp_cli_original_defined_vars = get_defined_vars(); - - eval( $this->get_wp_config_code() ); // phpcs:ignore Squiz.PHP.Eval.Discouraged - + eval( $this->get_wp_config_code() ); foreach ( get_defined_vars() as $key => $var ) { if ( array_key_exists( $key, $wp_cli_original_defined_vars ) || 'wp_cli_original_defined_vars' === $key ) { continue; } - - // phpcs:ignore PHPCompatibility.Variables.ForbiddenGlobalVariableVariable.NonBareVariableFound global ${$key}; - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound ${$key} = $var; } @@ -1861,7 +1104,7 @@ public function load_wordpress() { $this->do_early_invoke( 'after_wp_config_load' ); // Prevent error notice from wp_guess_url() when core isn't installed - if ( $this->cmd_starts_with( [ 'core', 'is-installed' ] ) + if ( $this->cmd_starts_with( array( 'core', 'is-installed' ) ) && ! defined( 'COOKIEHASH' ) ) { define( 'COOKIEHASH', md5( 'wp-cli' ) ); } @@ -1873,65 +1116,30 @@ public function load_wordpress() { $this->setup_bootstrap_hooks(); // Load Core, mu-plugins, plugins, themes etc. - - if ( $this->cmd_starts_with( [ 'help' ] ) ) { - // Hack: define `WP_DEBUG` and `WP_DEBUG_DISPLAY` to get `wpdb::bail()` to `wp_die()`. - if ( ! defined( 'WP_DEBUG' ) ) { - define( 'WP_DEBUG', true ); - } - if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) { - define( 'WP_DEBUG_DISPLAY', true ); - } - } - - // For multisite, set a pseudo WP_Screen to make is_admin() return true. - // This ensures ms_not_installed() shows detailed error messages instead of - // the generic "Error establishing a database connection" message. - if ( $this->is_multisite() ) { - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional temporary override for error messaging. - $GLOBALS['current_screen'] = new class() { - public function in_admin() { - return true; + if ( Utils\wp_version_compare( '4.6-alpha-37575', '>=' ) ) { + if ( $this->cmd_starts_with( array( 'help' ) ) ) { + // Hack: define `WP_DEBUG` and `WP_DEBUG_DISPLAY` to get `wpdb::bail()` to `wp_die()`. + if ( ! defined( 'WP_DEBUG' ) ) { + define( 'WP_DEBUG', true ); } - }; - - WP_CLI::add_wp_hook( - 'ms_loaded', - static function () { - // Clean up the pseudo screen object after the network has loaded - if ( isset( $GLOBALS['current_screen'] ) && ! ( $GLOBALS['current_screen'] instanceof \WP_Screen ) ) { - unset( $GLOBALS['current_screen'] ); - } + if ( ! defined( 'WP_DEBUG_DISPLAY' ) ) { + define( 'WP_DEBUG_DISPLAY', true ); } - ); + } + require ABSPATH . 'wp-settings.php'; + } else { + require WP_CLI_ROOT . '/php/wp-settings-cli.php'; } - // Save the current autoloaders so they can be restored after WordPress - // loads. This ensures that WP-CLI and package autoloaders take - // precedence over autoloaders registered by plugins. - $wp_cli_autoloaders = spl_autoload_functions() ?: []; - - require ABSPATH . 'wp-settings.php'; + // Fix memory limit. See http://core.trac.wordpress.org/ticket/14889 + ini_set( 'memory_limit', -1 ); // Load all the admin APIs, for convenience require ABSPATH . 'wp-admin/includes/admin.php'; - // Restore WP-CLI autoloaders to the front of the stack so they take - // precedence over plugin autoloaders that may have been registered - // during wp-settings.php loading. - $current_autoloaders = spl_autoload_functions(); - foreach ( array_reverse( $wp_cli_autoloaders ) as $autoloader ) { - // Can be false prior to PHP 8.0. - // @phpstan-ignore function.alreadyNarrowedType - if ( is_array( $current_autoloaders ) && in_array( $autoloader, $current_autoloaders, true ) ) { - spl_autoload_unregister( $autoloader ); - } - spl_autoload_register( $autoloader, true, true ); - } - - WP_CLI::add_wp_hook( + add_filter( 'filesystem_method', - static function () { + function() { return 'direct'; }, 99 @@ -1944,44 +1152,43 @@ static function () { WP_CLI::debug( 'Loaded WordPress', 'bootstrap' ); WP_CLI::do_hook( 'after_wp_load' ); + } - private static function fake_current_site_blog( $url_parts ): void { + private static function fake_current_site_blog( $url_parts ) { global $current_site, $current_blog; if ( ! isset( $url_parts['path'] ) ) { $url_parts['path'] = '/'; } - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional override. - $current_site = (object) [ - 'id' => 1, - 'blog_id' => 1, - 'domain' => $url_parts['host'], - 'path' => $url_parts['path'], + $current_site = (object) array( + 'id' => 1, + 'blog_id' => 1, + 'domain' => $url_parts['host'], + 'path' => $url_parts['path'], 'cookie_domain' => $url_parts['host'], - 'site_name' => 'WordPress', - ]; - - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional override. - $current_blog = (object) [ - 'blog_id' => 1, - 'site_id' => 1, - 'domain' => $url_parts['host'], - 'path' => $url_parts['path'], - 'public' => '1', + 'site_name' => 'Fake Site', + ); + + $current_blog = (object) array( + 'blog_id' => 1, + 'site_id' => 1, + 'domain' => $url_parts['host'], + 'path' => $url_parts['path'], + 'public' => '1', 'archived' => '0', - 'mature' => '0', - 'spam' => '0', - 'deleted' => '0', - 'lang_id' => '0', - ]; + 'mature' => '0', + 'spam' => '0', + 'deleted' => '0', + 'lang_id' => '0', + ); } /** * Called after wp-config.php is eval'd, to potentially reset `--url` */ - private function maybe_update_url_from_domain_constant(): void { + private function maybe_update_url_from_domain_constant() { if ( ! empty( $this->config['url'] ) || ! empty( $this->config['blog'] ) ) { return; } @@ -1991,38 +1198,26 @@ private function maybe_update_url_from_domain_constant(): void { if ( defined( 'PATH_CURRENT_SITE' ) ) { $url .= PATH_CURRENT_SITE; } - WP_CLI::set_url( $url ); + \WP_CLI::set_url( $url ); } } /** * Set up hooks meant to run during the WordPress bootstrap process */ - private function setup_bootstrap_hooks(): void { + private function setup_bootstrap_hooks() { if ( $this->config['skip-plugins'] ) { $this->setup_skip_plugins_filters(); } if ( $this->config['skip-themes'] ) { - WP_CLI::add_wp_hook( 'setup_theme', [ $this, 'action_setup_theme_wp_cli_skip_themes' ], 999 ); + WP_CLI::add_wp_hook( 'setup_theme', array( $this, 'action_setup_theme_wp_cli_skip_themes' ), 999 ); } - // Log WordPress HTTP API requests - WP_CLI::add_wp_hook( - 'pre_http_request', - static function ( $response, $args, $url ) { - $method = isset( $args['method'] ) ? $args['method'] : 'GET'; - WP_CLI::debug( sprintf( 'HTTP %s request to %s', $method, $url ), 'http' ); - return $response; - }, - 10, - 3 - ); - - if ( $this->cmd_starts_with( [ 'help' ] ) ) { + if ( $this->cmd_starts_with( array( 'help' ) ) ) { // Try to trap errors on help. - $help_handler = [ $this, 'help_wp_die_handler' ]; // Avoid any cross PHP version issues by not using $this in anon function. + $help_handler = array( $this, 'help_wp_die_handler' ); // Avoid any cross PHP version issues by not using $this in anon function. WP_CLI::add_wp_hook( 'wp_die_handler', function () use ( $help_handler ) { @@ -2032,38 +1227,18 @@ function () use ( $help_handler ) { } else { WP_CLI::add_wp_hook( 'wp_die_handler', - static function () { + function() { return '\WP_CLI\Utils\wp_die_handler'; } ); } // Prevent code from performing a redirect - WP_CLI::add_wp_hook( - 'wp_redirect', - function () { - ob_start(); - debug_print_backtrace(); - $backtrace = (string) ob_get_clean(); - - $message = sprintf( - 'Some code is trying to do a URL redirect. Backtrace: %s', - $backtrace - ); - - if ( Context::ADMIN === $this->context_manager->get_context() ) { - WP_CLI::debug( $message, 'bootstrap' ); - } else { - WP_CLI::warning( $message ); - } - - return false; - } - ); + WP_CLI::add_wp_hook( 'wp_redirect', 'WP_CLI\\Utils\\wp_redirect_handler' ); WP_CLI::add_wp_hook( 'nocache_headers', - static function ( $headers ) { + function( $headers ) { // WordPress might be calling nocache_headers() because of a dead db global $wpdb; if ( ! empty( $wpdb->error ) ) { @@ -2075,26 +1250,11 @@ static function ( $headers ) { } ); - WP_CLI::add_wp_hook( - 'setup_theme', - static function () { - // Polyfill is_customize_preview(), as it is needed by TwentyTwenty to - // check for starter content. - if ( ! function_exists( 'is_customize_preview' ) ) { - // @phpstan-ignore function.inner - function is_customize_preview() { - return false; - } - } - }, - 0 - ); - // ALTERNATE_WP_CRON might trigger a redirect, which we can't handle if ( defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) { WP_CLI::add_wp_hook( 'muplugins_loaded', - static function () { + function() { remove_action( 'init', 'wp_cron' ); } ); @@ -2102,18 +1262,18 @@ static function () { // Get rid of warnings when converting single site to multisite if ( defined( 'WP_INSTALLING' ) && $this->is_multisite() ) { - $values = [ - 'ms_files_rewriting' => null, - 'active_sitewide_plugins' => [], - '_site_transient_update_core' => null, - '_site_transient_update_themes' => null, + $values = array( + 'ms_files_rewriting' => null, + 'active_sitewide_plugins' => array(), + '_site_transient_update_core' => null, + '_site_transient_update_themes' => null, '_site_transient_update_plugins' => null, - 'WPLANG' => '', - ]; + 'WPLANG' => '', + ); foreach ( $values as $key => $value ) { WP_CLI::add_wp_hook( "pre_site_option_$key", - static function () use ( $values, $key ) { + function () use ( $values, $key ) { return $values[ $key ]; } ); @@ -2126,7 +1286,7 @@ static function () use ( $values, $key ) { // Always permit operations against WordPress, regardless of maintenance mode WP_CLI::add_wp_hook( 'enable_maintenance_mode', - static function () { + function() { return false; } ); @@ -2134,7 +1294,7 @@ static function () { // Use our own debug mode handling instead of WP core WP_CLI::add_wp_hook( 'enable_wp_debug_mode_checks', - static function ( $ret ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- WP core hook. + function( $ret ) { Utils\wp_debug_mode(); return false; } @@ -2143,18 +1303,18 @@ static function ( $ret ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionPa // Never load advanced-cache.php drop-in when WP-CLI is operating WP_CLI::add_wp_hook( 'enable_loading_advanced_cache_dropin', - static function () { + function() { return false; } ); - // In a multisite installation, die if unable to find site given in --url parameter + // In a multisite install, die if unable to find site given in --url parameter if ( $this->is_multisite() ) { $run_on_site_not_found = false; - if ( $this->cmd_starts_with( [ 'cache', 'flush' ] ) ) { + if ( $this->cmd_starts_with( array( 'cache', 'flush' ) ) ) { $run_on_site_not_found = 'cache flush'; } - if ( $this->cmd_starts_with( [ 'search-replace' ] ) ) { + if ( $this->cmd_starts_with( array( 'search-replace' ) ) ) { // Table-specified // Bits: search-replace [...] // Or not against a specific blog @@ -2165,15 +1325,16 @@ static function () { $run_on_site_not_found = 'search-replace'; } } - if ( $run_on_site_not_found ) { + if ( $run_on_site_not_found + && Utils\wp_version_compare( '4.0', '>=' ) ) { WP_CLI::add_wp_hook( 'ms_site_not_found', - static function () use ( $run_on_site_not_found ) { + function() use ( $run_on_site_not_found ) { // esc_sql() isn't yet loaded, but needed. if ( 'search-replace' === $run_on_site_not_found ) { require_once ABSPATH . WPINC . '/formatting.php'; } - // PHP 5.3 compatible implementation of run_command_and_exit(). + // PHP 5.3 compatible implementation of _run_command_and_exit(). $runner = WP_CLI::get_runner(); $runner->run_command( $runner->arguments, $runner->assoc_args ); exit; @@ -2183,11 +1344,11 @@ static function () use ( $run_on_site_not_found ) { } WP_CLI::add_wp_hook( 'ms_site_not_found', - static function ( $current_site, $domain, $path ) { - $url = $domain . $path; - $message = $url ? "Site '{$url}' not found." : 'Site not found.'; - $has_param = isset( WP_CLI::get_runner()->config['url'] ); - $has_const = defined( 'DOMAIN_CURRENT_SITE' ); + function( $current_site, $domain, $path ) { + $url = $domain . $path; + $message = $url ? "Site '{$url}' not found." : 'Site not found.'; + $has_param = isset( WP_CLI::get_runner()->config['url'] ); + $has_const = defined( 'DOMAIN_CURRENT_SITE' ); $explanation = ''; if ( $has_param ) { $explanation = 'Verify `--url=` matches an existing site.'; @@ -2198,31 +1359,20 @@ static function ( $current_site, $domain, $path ) { $explanation = 'Verify DOMAIN_CURRENT_SITE matches an existing site or use `--url=` to override.'; } } - $message .= ' ' . $explanation; + if ( $explanation ) { + $message .= ' ' . $explanation; + } WP_CLI::error( $message ); }, 10, 3 ); - - // Handle ms_network_not_found to provide better error messages - WP_CLI::add_wp_hook( - 'ms_network_not_found', - static function ( $domain, $path ) { - $url = $domain . $path; - $message = $url ? "Network '{$url}' not found." : 'Network not found.'; - $message .= ' Verify the network exists in the database or run `wp core multisite-install`.'; - WP_CLI::error( $message ); - }, - 10, - 2 - ); } // The APC cache is not available on the command-line, so bail, to prevent cache poisoning WP_CLI::add_wp_hook( 'muplugins_loaded', - static function () { + function() { if ( $GLOBALS['_wp_using_ext_object_cache'] && class_exists( 'APC_Object_Cache' ) ) { WP_CLI::warning( 'Running WP-CLI while the APC object cache is activated can result in cache corruption.' ); WP_CLI::confirm( 'Given the consequences, do you wish to continue?' ); @@ -2236,19 +1386,10 @@ static function () { $config = $this->config; WP_CLI::add_wp_hook( 'init', - static function () use ( $config ) { + function() use ( $config ) { if ( isset( $config['user'] ) ) { - $fetcher = new Fetchers\User(); - - /** - * @var string $user - */ - $user = $config['user']; - - /** - * @var \WP_User $user - */ - $user = $fetcher->get_check( $user ); + $fetcher = new \WP_CLI\Fetchers\User; + $user = $fetcher->get_check( $config['user'] ); wp_set_current_user( $user->ID ); } else { add_action( 'init', 'kses_remove_filters', 11 ); @@ -2261,10 +1402,10 @@ static function () use ( $config ) { // Avoid uncaught exception when using wp_mail() without defined $_SERVER['SERVER_NAME'] WP_CLI::add_wp_hook( 'wp_mail_from', - static function ( $from_email ) { + function( $from_email ) { if ( 'wordpress@' === $from_email ) { - $sitename = strtolower( (string) Utils\parse_url( site_url(), PHP_URL_HOST ) ); - if ( substr( $sitename, 0, 4 ) === 'www.' ) { + $sitename = strtolower( parse_url( site_url(), PHP_URL_HOST ) ); + if ( substr( $sitename, 0, 4 ) == 'www.' ) { $sitename = substr( $sitename, 4 ); } $from_email = 'wordpress@' . $sitename; @@ -2273,58 +1414,68 @@ static function ( $from_email ) { } ); - // Set up hook for plugins and themes to conditionally add WP-CLI commands. + // Don't apply set_url_scheme in get_site_url() WP_CLI::add_wp_hook( - 'init', - static function () { - do_action( 'cli_init' ); - } + 'site_url', + function( $url, $path, $scheme, $blog_id ) { + if ( empty( $blog_id ) || ! is_multisite() ) { + $url = get_option( 'siteurl' ); + } else { + switch_to_blog( $blog_id ); + $url = get_option( 'siteurl' ); + restore_current_blog(); + } + if ( $path && is_string( $path ) ) { + $url .= '/' . ltrim( $path, '/' ); + } + return $url; + }, + 0, + 4 ); + } /** * Set up the filters to skip the loaded plugins */ private function setup_skip_plugins_filters() { - $wp_cli_filter_active_plugins = static function ( $plugins ) { - /** - * @var array $plugins - */ + $wp_cli_filter_active_plugins = function( $plugins ) { $skipped_plugins = WP_CLI::get_runner()->config['skip-plugins']; if ( true === $skipped_plugins ) { - return []; + return array(); } if ( ! is_array( $plugins ) ) { return $plugins; } foreach ( $plugins as $a => $b ) { // active_sitewide_plugins stores plugin name as the key. - if ( false !== strpos( (string) current_filter(), 'active_sitewide_plugins' ) && Utils\is_plugin_skipped( (string) $a ) ) { + if ( false !== strpos( current_filter(), 'active_sitewide_plugins' ) && Utils\is_plugin_skipped( $a ) ) { unset( $plugins[ $a ] ); // active_plugins stores plugin name as the value. - } elseif ( false !== strpos( (string) current_filter(), 'active_plugins' ) && Utils\is_plugin_skipped( (string) $b ) ) { + } elseif ( false !== strpos( current_filter(), 'active_plugins' ) && Utils\is_plugin_skipped( $b ) ) { unset( $plugins[ $a ] ); } } // Reindex because active_plugins expects a numeric index. - if ( false !== strpos( (string) current_filter(), 'active_plugins' ) ) { + if ( false !== strpos( current_filter(), 'active_plugins' ) ) { $plugins = array_values( $plugins ); } return $plugins; }; - $hooks = [ + $hooks = array( 'pre_site_option_active_sitewide_plugins', 'site_option_active_sitewide_plugins', 'pre_option_active_plugins', 'option_active_plugins', - ]; + ); foreach ( $hooks as $hook ) { WP_CLI::add_wp_hook( $hook, $wp_cli_filter_active_plugins, 999 ); } WP_CLI::add_wp_hook( 'plugins_loaded', - static function () use ( $hooks, $wp_cli_filter_active_plugins ) { + function() use ( $hooks, $wp_cli_filter_active_plugins ) { foreach ( $hooks as $hook ) { remove_filter( $hook, $wp_cli_filter_active_plugins, 999 ); } @@ -2337,7 +1488,7 @@ static function () use ( $hooks, $wp_cli_filter_active_plugins ) { * Set up the filters to skip the loaded theme */ public function action_setup_theme_wp_cli_skip_themes() { - $wp_cli_filter_active_theme = static function ( $value ) { + $wp_cli_filter_active_theme = function( $value ) { $skipped_themes = WP_CLI::get_runner()->config['skip-themes']; if ( true === $skipped_themes ) { return ''; @@ -2349,69 +1500,45 @@ public function action_setup_theme_wp_cli_skip_themes() { $checked_value = $value; // Always check against the stylesheet value // This ensures a child theme can be skipped when template differs - if ( false !== stripos( (string) current_filter(), 'option_template' ) ) { + if ( false !== stripos( current_filter(), 'option_template' ) ) { $checked_value = get_option( 'stylesheet' ); } - if ( '' === $checked_value || in_array( $checked_value, $skipped_themes, true ) ) { + if ( '' === $checked_value || in_array( $checked_value, $skipped_themes ) ) { return ''; } return $value; }; - $hooks = [ + $hooks = array( 'pre_option_template', 'option_template', 'pre_option_stylesheet', 'option_stylesheet', - ]; + ); foreach ( $hooks as $hook ) { add_filter( $hook, $wp_cli_filter_active_theme, 999 ); } - - // Noop memoization added in WP 6.4. - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global. - $GLOBALS['wp_stylesheet_path'] = null; - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global. - $GLOBALS['wp_template_path'] = null; - - // Remove theme-related actions not directly tied into the theme lifecycle. - if ( WP_CLI::get_runner()->config['skip-themes'] ) { - $theme_related_actions = [ - [ 'init', '_register_theme_block_patterns' ], // Block patterns registration in WP Core. - [ 'init', 'gutenberg_register_theme_block_patterns' ], // Block patterns registration in the GB plugin. - ]; - foreach ( $theme_related_actions as $action ) { - list( $hook, $callback ) = $action; - remove_action( $hook, $callback ); - } - } - // Clean up after the TEMPLATEPATH and STYLESHEETPATH constants are defined WP_CLI::add_wp_hook( 'after_setup_theme', - static function () use ( $hooks, $wp_cli_filter_active_theme ) { + function() use ( $hooks, $wp_cli_filter_active_theme ) { foreach ( $hooks as $hook ) { remove_filter( $hook, $wp_cli_filter_active_theme, 999 ); } - // Noop memoization added in WP 6.4 again. - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global. - $GLOBALS['wp_stylesheet_path'] = null; - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global. - $GLOBALS['wp_template_path'] = null; }, 0 ); } /** - * Whether or not this WordPress installation is multisite. + * Whether or not this WordPress install is multisite. * * For use after wp-config.php has loaded, but before the rest of WordPress * is loaded. */ - private function is_multisite(): bool { + private function is_multisite() { if ( defined( 'MULTISITE' ) ) { - return MULTISITE; // @phpstan-ignore phpstanWP.wpConstant.fetch + return MULTISITE; } if ( defined( 'SUBDOMAIN_INSTALL' ) || defined( 'VHOST' ) || defined( 'SUNRISE' ) ) { @@ -2426,30 +1553,25 @@ private function is_multisite(): bool { */ public function help_wp_die_handler( $message ) { $help_exit_warning = 'Error during WordPress load.'; - if ( $message instanceof WP_Error ) { - $help_exit_warning = Utils\wp_clean_error_message( $message->get_error_message() ); + if ( $message instanceof \WP_Error ) { + $help_exit_warning = WP_CLI\Utils\wp_clean_error_message( $message->get_error_message() ); } elseif ( is_string( $message ) ) { - $help_exit_warning = Utils\wp_clean_error_message( $message ); + $help_exit_warning = WP_CLI\Utils\wp_clean_error_message( $message ); } - $this->run_command_and_exit( $help_exit_warning ); + $this->_run_command_and_exit( $help_exit_warning ); } /** * Check whether there's a WP-CLI update available, and suggest update if so. */ - private function auto_check_update(): void { + private function auto_check_update() { // `wp cli update` only works with Phars at this time. - if ( ! Path::inside_phar() ) { + if ( ! Utils\inside_phar() ) { return; } - /** - * @var array $argv - */ - $argv = $_SERVER['argv']; - - $existing_phar = (string) realpath( (string) $argv[0] ); + $existing_phar = realpath( $_SERVER['argv'][0] ); // Phar needs to be writable to be easily updateable. if ( ! is_writable( $existing_phar ) || ! is_writable( dirname( $existing_phar ) ) ) { return; @@ -2461,40 +1583,40 @@ private function auto_check_update(): void { } // Allow hosts and other providers to disable automatic check update. - if ( Utils\get_env_or_config( 'WP_CLI_DISABLE_AUTO_CHECK_UPDATE' ) ) { + if ( getenv( 'WP_CLI_DISABLE_AUTO_CHECK_UPDATE' ) ) { return; } // Permit configuration of number of days between checks. - $days_between_checks = Utils\get_env_or_config( 'WP_CLI_AUTO_CHECK_UPDATE_DAYS' ); + $days_between_checks = getenv( 'WP_CLI_AUTO_CHECK_UPDATE_DAYS' ); if ( false === $days_between_checks ) { $days_between_checks = 1; } - $cache = WP_CLI::get_cache(); + $cache = WP_CLI::get_cache(); $cache_key = 'wp-cli-update-check'; // Bail early on the first check, so we don't always check on an unwritable cache. if ( ! $cache->has( $cache_key ) ) { - $cache->write( $cache_key, (string) time() ); + $cache->write( $cache_key, time() ); return; } // Bail if last check is still within our update check time period. $last_check = (int) $cache->read( $cache_key ); - if ( ( time() - ( 24 * 60 * 60 * (int) $days_between_checks ) ) < $last_check ) { + if ( time() - ( 24 * 60 * 60 * $days_between_checks ) < $last_check ) { return; } // In case the operation fails, ensure the timestamp has been updated. - $cache->write( $cache_key, (string) time() ); + $cache->write( $cache_key, time() ); // Check whether any updates are available. ob_start(); WP_CLI::run_command( - [ 'cli', 'check-update' ], - [ + array( 'cli', 'check-update' ), + array( 'format' => 'count', - ] + ) ); $count = ob_get_clean(); if ( ! $count ) { @@ -2502,95 +1624,25 @@ private function auto_check_update(): void { } // Looks like an update is available, so let's prompt to update. - $update_args = []; - // Allow skipping the confirmation prompt via environment variable. - if ( Utils\get_env_or_config( 'WP_CLI_AUTO_UPDATE_PROMPT' ) === 'no' ) { - $update_args['yes'] = true; - } - - // Get the current Phar's modification time before the update. - $phar_mtime_before = filemtime( $existing_phar ); - - WP_CLI::run_command( [ 'cli', 'update' ], $update_args ); - - // Check if the Phar was actually updated by comparing modification times. - clearstatcache( true, $existing_phar ); - $phar_mtime_after = filemtime( $existing_phar ); - if ( $phar_mtime_after > $phar_mtime_before ) { - // After update, re-execute the original command with the new Phar. - $this->rerun_command_after_update(); - } - } - - /** - * Re-execute the original command with the updated Phar. - * - * This method is called after a successful auto-update to transparently - * continue with the user's original command using the new Phar version. - */ - private function rerun_command_after_update(): void { - /** - * @var string[] $original_args - */ - $original_args = array_slice( (array) $GLOBALS['argv'], 1 ); - - // Skip re-execution if the original command was a CLI command - // to avoid infinite loops or redundant execution. - // Use $this->arguments instead of $original_args to properly handle aliases. - if ( ! empty( $this->arguments ) && 'cli' === $this->arguments[0] ) { - exit( 0 ); - } - - // Skip re-execution if there are no arguments (just running `wp` with no command). - if ( empty( $original_args ) ) { - exit( 0 ); - } - - /** - * @var string[] $argv - */ - $argv = $_SERVER['argv']; - - // Get the path to the current (now updated) Phar. - $phar_path = realpath( $argv[0] ); - if ( false === $phar_path ) { - WP_CLI::error( 'Failed to determine the path to the WP-CLI Phar.' ); - } - - // Build the command to re-execute. - $php_binary = Utils\get_php_binary(); - $escaped_args = array_map( 'escapeshellarg', $original_args ); - $command = sprintf( - '%s %s %s', - escapeshellarg( $php_binary ), - escapeshellarg( $phar_path ), - implode( ' ', $escaped_args ) - ); - - WP_CLI::debug( 'Re-executing command after update.', 'bootstrap' ); - - // Execute the command and pass through the exit code. - passthru( $command, $exit_code ); - exit( $exit_code ); + WP_CLI::run_command( array( 'cli', 'update' ) ); + // If the Phar was replaced, we can't proceed with the original process. + exit; } /** * Get a suggestion on similar (sub)commands when the user entered an * unknown (sub)command. * - * @param string $entry User entry that didn't match an - * existing command. - * @param CompositeCommand|null $root_command Root command to start search for - * suggestions at. + * @param string $entry User entry that didn't match an + * existing command. + * @param CompositeCommand $root_command Root command to start search for + * suggestions at. * * @return string Suggestion that fits the user entry, or an empty string. */ - private function get_subcommand_suggestion( $entry, $root_command = null ) { - $commands = []; - if ( ( $root_command instanceof CompositeCommand ) === false ) { - $root_command = WP_CLI::get_root_command(); - } - $this->enumerate_commands( $root_command, $commands ); + private function get_subcommand_suggestion( $entry, CompositeCommand $root_command = null ) { + $commands = array(); + $this->enumerate_commands( $root_command ?: \WP_CLI::get_root_command(), $commands ); return Utils\get_suggestion( $entry, $commands, $threshold = 2 ); } @@ -2602,7 +1654,7 @@ private function get_subcommand_suggestion( $entry, $root_command = null ) { * @param array $list Reference to list accumulating results. * @param string $parent Parent command to use as prefix. */ - private function enumerate_commands( CompositeCommand $command, array &$list, $parent = '' ): void { + private function enumerate_commands( CompositeCommand $command, array &$list, $parent = '' ) { foreach ( $command->get_subcommands() as $subcommand ) { /** @var CompositeCommand $subcommand */ $command_string = empty( $parent ) @@ -2618,11 +1670,11 @@ private function enumerate_commands( CompositeCommand $command, array &$list, $p /** * Enables (almost) full PHP error reporting to stderr. */ - private function enable_error_reporting(): void { + private function enable_error_reporting() { if ( E_ALL !== error_reporting() ) { // Don't enable E_DEPRECATED as old versions of WP use PHP 4 style constructors and the mysql extension. error_reporting( E_ALL & ~E_DEPRECATED ); } - ini_set( 'display_errors', 'stderr' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed + ini_set( 'display_errors', 'stderr' ); } } diff --git a/php/WP_CLI/ShutdownHandler.php b/php/WP_CLI/ShutdownHandler.php deleted file mode 100644 index 142d228ed..000000000 --- a/php/WP_CLI/ShutdownHandler.php +++ /dev/null @@ -1,388 +0,0 @@ - $plugin ]; - } elseif ( 'functions.php' === $theme ) { - $message .= "\n\nAn unexpected functions.php file in the themes directory may have caused this internal server error."; - - // This error cannot be skipped with `--skip-themes`. - return $message; - } elseif ( $theme ) { - $message .= "\n\nThis error may have been caused by the theme {$theme}."; - $message .= "\nTo skip this theme, run the command again with:"; - $message .= "\n --skip-themes={$theme}"; - - $skip = [ 'skip-themes' => $theme ]; - } else { - $message .= "\n\nThis error may have been caused by a theme or plugin."; - $message .= "\nTo skip all plugins and themes, run the command again with:"; - $message .= "\n --skip-plugins --skip-themes"; - $skip = [ - 'skip-plugins' => true, - 'skip-themes' => true, - ]; - } - - if ( ! self::should_handle_error_rerun() ) { - return $message; - } - - WP_CLI::add_wp_hook( - 'wp_die_handler', - function () use ( $skip ) { - return static function ( $wp_error ) use ( $skip ) { - WP_CLI::error( $wp_error->get_error_message(), false ); - - self::prompt_and_rerun( $skip ); - }; - } - ); - - return $message; - } - - /** - * Identify the plugin causing the error. - * - * @param string $file File path where error occurred. - * @return string|null Plugin slug, or null if not a plugin error. - */ - private static function identify_plugin( $file ) { - // Normalize path separators for consistent matching - $file = Path::normalize( $file ); - - // Use WordPress constants if available for more accurate path detection - if ( defined( 'WP_PLUGIN_DIR' ) ) { - $slug = self::extract_component_slug( $file, WP_PLUGIN_DIR ); - if ( $slug ) { - return $slug; - } - } - - if ( defined( 'WPMU_PLUGIN_DIR' ) ) { - $slug = self::extract_component_slug( $file, WPMU_PLUGIN_DIR ); - if ( $slug ) { - return $slug; - } - } - - // Fallback to pattern matching if constants are not available - if ( preg_match( '#/wp-content/(?:mu-)?plugins/([^/]+)/#', $file, $matches ) ) { - return $matches[1]; - } - - // Check for direct single-file plugins - if ( preg_match( '#/wp-content/(?:mu-)?plugins/([^/]+)\\.php$#', $file, $matches ) ) { - return $matches[1]; - } - - return null; - } - - /** - * Identify the theme causing the error. - * - * @param string $file File path where error occurred. - * @return string|null Theme slug, or null if not a theme error. - */ - private static function identify_theme( $file ) { - // Normalize path separators for consistent matching - $file = Path::normalize( $file ); - - // Use get_theme_root() if available for more accurate path detection - if ( function_exists( 'get_theme_root' ) ) { - $slug = self::extract_theme_slug( $file, get_theme_root() ); - if ( $slug ) { - return $slug; - } - } elseif ( defined( 'WP_CONTENT_DIR' ) ) { - // Fallback to WP_CONTENT_DIR/themes if get_theme_root() is not available - $slug = self::extract_theme_slug( $file, WP_CONTENT_DIR . '/themes' ); - if ( $slug ) { - return $slug; - } - } - - // Fallback to pattern matching if constants/functions are not available - if ( preg_match( '#/wp-content/themes/([^/]+)/#', $file, $matches ) ) { - return $matches[1]; - } - - // Check for themes/functions.php directly in the themes directory - if ( preg_match( '#/wp-content/themes/(functions\\.php)$#', $file, $matches ) ) { - return $matches[1]; - } - - return null; - } - - /** - * Extract component slug from a file path given a base directory. - * - * @param string $file File path where error occurred. - * @param string $base_dir Base directory path. - * @return string|null Component slug, or null if not found. - */ - private static function extract_component_slug( $file, $base_dir ) { - $file = Path::normalize( $file ); - $base_dir = Path::normalize( $base_dir ); - - if ( 0 === strpos( $file, $base_dir . '/' ) ) { - $relative = substr( $file, strlen( $base_dir ) + 1 ); - $parts = explode( '/', $relative ); - if ( ! empty( $parts[0] ) ) { - // For components in subdirectories, return the directory name - // For single-file components, return the filename without .php - return false !== strpos( $parts[0], '.php' ) ? basename( $parts[0], '.php' ) : $parts[0]; - } - } - return null; - } - - /** - * Extract theme slug from a file path given a theme directory. - * - * @param string $file File path where error occurred. - * @param string $theme_dir Theme directory path. - * @return string|null Theme slug, or null if not found. - */ - private static function extract_theme_slug( $file, $theme_dir ) { - $file = Path::normalize( $file ); - $theme_dir = Path::normalize( $theme_dir ); - - if ( 0 === strpos( $file, $theme_dir . '/' ) ) { - $relative = substr( $file, strlen( $theme_dir ) + 1 ); - $parts = explode( '/', $relative ); - if ( ! empty( $parts[0] ) ) { - return $parts[0]; - } - } - return null; - } - - /** - * Check if we should setup the error rerun handler. - * - * @return bool - */ - private static function should_handle_error_rerun() { - // Check environment variable WP_CLI_ERROR_RERUN - $error_rerun = Utils\get_env_or_config( 'WP_CLI_ERROR_RERUN' ); - - if ( false !== $error_rerun ) { - return 'no' !== $error_rerun; - } - - // Default: handle the error rerun (prompt) - return true; - } - - /** - * Prompt the user to rerun the command with the skip flag. - * - * @param array $skip Skip flag(s) to append. - */ - private static function prompt_and_rerun( $skip ) { - // Get environment variable to check default behavior - $error_rerun = Utils\get_env_or_config( 'WP_CLI_ERROR_RERUN' ); - - // If set to 'yes', automatically rerun without prompting - if ( 'yes' === $error_rerun ) { - self::rerun_with_skip( $skip ); - return; - } - - // If set to 'no', don't prompt or rerun at all - if ( 'no' === $error_rerun ) { - return; - } - - // 'prompt' or default behavior - $skip_string = self::get_skip_string( $skip ); - - try { - WP_CLI::confirm( "\nWould you like to run the command again with $skip_string?" ); - self::rerun_with_skip( $skip ); - } catch ( \WP_CLI\ExitException $e ) { - // User declined or Ctrl+C - exit gracefully - WP_CLI::line( 'Command not rerun.' ); - } - } - - /** - * Return a formatted --skip-[...] string. - * - * @param array $skip Skip flag(s) to append. - * @return string - */ - private static function get_skip_string( $skip ) { - return implode( - ' ', - array_map( - static function ( $key, $value ) { - return is_bool( $value ) ? "--$key" : "--$key=$value"; - }, - array_keys( $skip ), - array_values( $skip ) - ) - ); - } - - /** - * Rerun the current command with the skip flag. - * - * Launches a subprocess so that WordPress is reloaded without the failing - * plugin or theme. Passing skip flags via $assoc_args to run_command() - * would cause a validation error because they are global parameters that - * are not part of any individual subcommand's synopsis. - * - * @param array $skip Skip flag(s) to append. - */ - private static function rerun_with_skip( $skip ) { - $runner = WP_CLI::get_runner(); - - if ( ! $runner ) { - return; - } - - $skip_string = self::get_skip_string( $skip ); - - // Use fwrite(STDOUT,...) rather than WP_CLI::line() / echo here: after - // WordPress's fatal-error handler clears all output buffers with - // ob_end_clean(), subsequent `echo` calls may be silently swallowed on - // some platforms (notably Windows) before proc_open starts the - // subprocess. Writing directly to the PHP STDOUT stream resource - // bypasses any C-library buffering and ensures the message reaches the - // pipe before the subprocess output does. - fwrite( STDOUT, "\nRerunning command with {$skip_string}...\n" ); - fflush( STDOUT ); - - $php_bin = escapeshellarg( Utils\get_php_binary() ); - - /** - * @var string[] $argv - */ - $argv = $GLOBALS['argv']; - $script_path = escapeshellarg( $argv[0] ); - - $args = implode( - ' ', - array_map( 'escapeshellarg', (array) $runner->arguments ) - ); - - $assoc_args_str = Utils\assoc_args_to_str( (array) $runner->assoc_args ); - - // Merge skip flags into the runtime config so they are treated as global - // parameters by the subprocess and validated correctly. - $runtime_config = (array) $runner->runtime_config; - foreach ( $skip as $skip_flag => $slug ) { - // $slug === true means "skip everything": set unconditionally. - if ( true === $slug ) { - $runtime_config[ $skip_flag ] = true; - continue; - } - - // If the existing config already skips everything, keep it as-is. - if ( isset( $runtime_config[ $skip_flag ] ) && true === $runtime_config[ $skip_flag ] ) { - continue; - } - - if ( isset( $runtime_config[ $skip_flag ] ) ) { - // Normalize arrays (e.g. from YAML list config) to a comma-separated string. - if ( is_array( $runtime_config[ $skip_flag ] ) ) { - $parts = []; - foreach ( $runtime_config[ $skip_flag ] as $item ) { - // @phpstan-ignore cast.string (array items from YAML config are mixed but safely castable to string) - $parts[] = (string) $item; - } - $existing = implode( ',', $parts ); - } else { - $existing = (string) $runtime_config[ $skip_flag ]; - } - - $runtime_config[ $skip_flag ] = '' !== $existing ? $existing . ',' . $slug : $slug; - } else { - $runtime_config[ $skip_flag ] = $slug; - } - } - $runtime_config_str = Utils\assoc_args_to_str( $runtime_config ); - - $full_command = "{$php_bin} {$script_path} {$args}{$assoc_args_str}{$runtime_config_str}"; - - $env = getenv() ?: []; - $env['WP_CLI_ERROR_RERUN'] = 'no'; // Prevent rerun recursion in the subprocess. - - $pipes = []; - $proc = Utils\proc_open_compat( $full_command, [ STDIN, STDOUT, STDERR ], $pipes, getcwd() ?: null, $env ); - - if ( is_resource( $proc ) ) { - $exit_code = proc_close( $proc ); - exit( $exit_code ); - } - - WP_CLI::error( 'Failed to launch subprocess for command rerun.' ); - } -} diff --git a/php/WP_CLI/SynopsisParser.php b/php/WP_CLI/SynopsisParser.php index a629c52b5..515eebfad 100644 --- a/php/WP_CLI/SynopsisParser.php +++ b/php/WP_CLI/SynopsisParser.php @@ -6,24 +6,17 @@ * Generate a synopsis from a command's PHPdoc arguments. * Turns something like "..." * into [ optional=>false, type=>positional, repeating=>true, name=>object-id ] - * - * @phpstan-import-type FlagParameter from \WP_CLI - * @phpstan-import-type AssocParameter from \WP_CLI - * @phpstan-import-type PositionalParameter from \WP_CLI - * @phpstan-import-type GenericParameter from \WP_CLI - * @phpstan-import-type UnknownParameter from \WP_CLI - * @phpstan-import-type CommandSynopsis from \WP_CLI */ class SynopsisParser { /** - * @param string $synopsis A synopsis + * @param string A synopsis * @return array List of parameters */ public static function parse( $synopsis ) { - $tokens = array_filter( (array) preg_split( '/[\s\t]+/', $synopsis ) ); + $tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) ); - $params = []; + $params = array(); foreach ( $tokens as $token ) { $param = self::classify_token( $token ); @@ -35,37 +28,26 @@ public static function parse( $synopsis ) { } $param['token'] = $token; - $params[] = $param; + $params[] = $param; } return $params; } /** - * Render the Synopsis into a format string. - * - * @param array $synopsis A structured synopsis. This might get reordered - * to match the parsed output. - * @return string Rendered synopsis. - * - * @phpstan-param CommandSynopsis[] $synopsis + * @param array A structured synopsis + * @return string Rendered synopsis */ - public static function render( &$synopsis ) { + public static function render( $synopsis ) { if ( ! is_array( $synopsis ) ) { return ''; } - $bits = [ + $bits = array( 'positional' => '', - 'assoc' => '', - 'generic' => '', - 'flag' => '', - ]; - $reordered_synopsis = [ - 'positional' => [], - 'assoc' => [], - 'generic' => [], - 'flag' => [], - ]; + 'assoc' => '', + 'generic' => '', + 'flag' => '', + ); foreach ( $bits as $key => &$value ) { foreach ( $synopsis as $arg ) { if ( empty( $arg['type'] ) @@ -78,47 +60,14 @@ public static function render( &$synopsis ) { } if ( 'positional' === $key ) { - /** - * @phpstan-var PositionalParameter $arg - */ $rendered_arg = "<{$arg['name']}>"; - - $reordered_synopsis['positional'] [] = $arg; } elseif ( 'assoc' === $key ) { - /** - * @phpstan-var AssocParameter $arg - */ $arg_value = isset( $arg['value']['name'] ) ? $arg['value']['name'] : $arg['name']; - $arg_value = "=<{$arg_value}>"; - - if ( ! empty( $arg['value']['optional'] ) ) { - $arg_value = "[{$arg_value}]"; - } - - $alias_suffix = ''; - if ( ! empty( $arg['aliases'] ) ) { - $alias_suffix = '|' . implode( '|', $arg['aliases'] ); - } - - $rendered_arg = "--{$arg['name']}{$arg_value}{$alias_suffix}"; - - $reordered_synopsis['assoc'] [] = $arg; + $rendered_arg = "--{$arg['name']}=<{$arg_value}>"; } elseif ( 'generic' === $key ) { $rendered_arg = '--='; - - $reordered_synopsis['generic'] [] = $arg; } elseif ( 'flag' === $key ) { - /** - * @phpstan-var FlagParameter $arg - */ - $alias_suffix = ''; - if ( ! empty( $arg['aliases'] ) ) { - $alias_suffix = '|' . implode( '|', $arg['aliases'] ); - } - - $rendered_arg = "--{$arg['name']}{$alias_suffix}"; - - $reordered_synopsis['flag'] [] = $arg; + $rendered_arg = "--{$arg['name']}"; } if ( ! empty( $arg['repeating'] ) ) { $rendered_arg = "{$rendered_arg}..."; @@ -129,15 +78,12 @@ public static function render( &$synopsis ) { $value .= "{$rendered_arg} "; } } - $rendered = implode( '', $bits ); - - $synopsis = array_merge( - $reordered_synopsis['positional'], - $reordered_synopsis['assoc'], - $reordered_synopsis['generic'], - $reordered_synopsis['flag'] - ); - + $rendered = ''; + foreach ( $bits as $v ) { + if ( ! empty( $v ) ) { + $rendered .= $v; + } + } return rtrim( $rendered, ' ' ); } @@ -145,170 +91,75 @@ public static function render( &$synopsis ) { * Classify argument attributes based on its syntax. * * @param string $token - * @return array - * - * @phpstan-return CommandSynopsis + * @return array $param */ private static function classify_token( $token ) { - list( $optional, $token ) = self::is_optional( $token ); - list( $repeating, $token ) = self::is_repeating( $token ); - - if ( '--=' === $token ) { - return [ - 'type' => 'generic', - 'optional' => $optional, - 'repeating' => $repeating, - ]; - } + $param = array(); - $value_name = null; - $value_optional = false; - if ( preg_match( '/\\[=<([a-zA-Z-_|,0-9]+)>\\]/', $token, $matches ) ) { - $value_name = $matches[1]; - $value_optional = true; - $token = str_replace( $matches[0], '', $token ); - } elseif ( preg_match( '/=<([a-zA-Z-_|,0-9]+)>/', $token, $matches ) ) { - $value_name = $matches[1]; - $value_optional = false; - $token = str_replace( $matches[0], '', $token ); - } - - list( $aliases, $token ) = self::extract_aliases( $token ); + list( $param['optional'], $token ) = self::is_optional( $token ); + list( $param['repeating'], $token ) = self::is_repeating( $token ); - $p_name = '([a-z-_0-9]+)'; + $p_name = '([a-z-_0-9]+)'; $p_value = '([a-zA-Z-_|,0-9]+)'; - if ( preg_match( "/^<($p_value)>$/", $token, $matches ) ) { - return [ - 'type' => 'positional', - 'name' => $matches[1], - 'optional' => $optional, - 'repeating' => $repeating, - ]; + if ( '--=' === $token ) { + $param['type'] = 'generic'; + } elseif ( preg_match( "/^<($p_value)>$/", $token, $matches ) ) { + $param['type'] = 'positional'; + $param['name'] = $matches[1]; } elseif ( preg_match( "/^--(?:\\[no-\\])?$p_name/", $token, $matches ) ) { - $name = $matches[1]; - - if ( null !== $value_name ) { - $param = [ - 'type' => 'assoc', - 'name' => $name, - 'optional' => $optional, - 'repeating' => $repeating, - 'value' => [ - 'optional' => $value_optional, - 'name' => $value_name, - ], - ]; - if ( ! empty( $aliases ) ) { - $param['aliases'] = $aliases; - } - return $param; - } + $param['name'] = $matches[1]; $value = substr( $token, strlen( $matches[0] ) ); - // substr can return false <= PHP 8.0. - // @phpstan-ignore identical.alwaysFalse + // substr returns false <= PHP 5.6, and '' PHP 7+ if ( false === $value || '' === $value ) { - $param = [ - 'type' => 'flag', - 'name' => $name, - 'optional' => $optional, - 'repeating' => $repeating, - ]; - if ( ! empty( $aliases ) ) { - $param['aliases'] = $aliases; - } - return $param; + $param['type'] = 'flag'; } else { - list( $value_optional, $value ) = self::is_optional( $value ); + $param['type'] = 'assoc'; - if ( preg_match( "/^=<$p_value>$/", $value, $matches_value ) ) { - $param = [ - 'type' => 'assoc', - 'name' => $name, - 'optional' => $optional, - 'repeating' => $repeating, - 'value' => [ - 'optional' => $value_optional, - 'name' => $matches_value[1], - ], - ]; - if ( ! empty( $aliases ) ) { - $param['aliases'] = $aliases; - } - return $param; - } - } - } + list( $param['value']['optional'], $value ) = self::is_optional( $value ); - return [ - 'type' => 'unknown', - 'optional' => $optional, - 'repeating' => $repeating, - ]; - } - - /** - * Extract pipe-separated aliases from a token. - * - * Given `--flag|alias1|alias2`, returns `[['alias1', 'alias2'], '--flag']`. - * The `|` separator inside `` brackets is ignored. - * - * @param string $token - * @return array{0: string[], 1: string} - */ - private static function extract_aliases( $token ) { - $depth = 0; - $len = strlen( $token ); - - for ( $i = 0; $i < $len; $i++ ) { - $char = $token[ $i ]; - if ( '<' === $char ) { - ++$depth; - } elseif ( '>' === $char ) { - --$depth; - } elseif ( '|' === $char && 0 === $depth ) { - $aliases = array_values( - array_filter( - explode( '|', substr( $token, $i + 1 ) ), - static function ( $alias ) { - return '' !== $alias; - } - ) - ); - return [ $aliases, substr( $token, 0, $i ) ]; + if ( preg_match( "/^=<$p_value>$/", $value, $matches ) ) { + $param['value']['name'] = $matches[1]; + } else { + $param = array( + 'type' => 'unknown', + ); + } } + } else { + $param['type'] = 'unknown'; } - return [ [], $token ]; + return $param; } /** * An optional parameter is surrounded by square brackets. * * @param string $token - * @return array{0: bool, 1: string} + * @return array */ private static function is_optional( $token ) { - if ( '[' === substr( $token, 0, 1 ) && ']' === substr( $token, -1 ) ) { - return [ true, substr( $token, 1, -1 ) ]; + if ( '[' == substr( $token, 0, 1 ) && ']' == substr( $token, -1 ) ) { + return array( true, substr( $token, 1, -1 ) ); } - return [ false, $token ]; + return array( false, $token ); } /** * A repeating parameter is followed by an ellipsis. * * @param string $token - * @return array{0: bool, 1: string} + * @return array */ private static function is_repeating( $token ) { if ( '...' === substr( $token, -3 ) ) { - return [ true, substr( $token, 0, -3 ) ]; + return array( true, substr( $token, 0, -3 ) ); } - return [ false, $token ]; + return array( false, $token ); } } diff --git a/php/WP_CLI/SynopsisValidator.php b/php/WP_CLI/SynopsisValidator.php index 3d308af2a..11614bf96 100644 --- a/php/WP_CLI/SynopsisValidator.php +++ b/php/WP_CLI/SynopsisValidator.php @@ -8,11 +8,9 @@ class SynopsisValidator { /** - * Structured representation of command synopsis. - * - * @var array + * @var array $spec Structured representation of command synopsis. */ - private $spec; + private $spec = array(); /** * @param string $synopsis Command's synopsis. @@ -29,11 +27,10 @@ public function __construct( $synopsis ) { public function get_unknown() { return array_column( $this->query_spec( - [ + array( 'type' => 'unknown', - ] - ), - 'token' + ) + ), 'token' ); } @@ -45,10 +42,10 @@ public function get_unknown() { */ public function enough_positionals( $args ) { $positional = $this->query_spec( - [ - 'type' => 'positional', + array( + 'type' => 'positional', 'optional' => false, - ] + ) ); return count( $args ) >= count( $positional ); @@ -62,22 +59,22 @@ public function enough_positionals( $args ) { */ public function unknown_positionals( $args ) { $positional_repeating = $this->query_spec( - [ - 'type' => 'positional', + array( + 'type' => 'positional', 'repeating' => true, - ] + ) ); // At least one positional supports as many as possible. if ( ! empty( $positional_repeating ) ) { - return []; + return array(); } $positional = $this->query_spec( - [ - 'type' => 'positional', + array( + 'type' => 'positional', 'repeating' => false, - ] + ) ); return array_slice( $args, count( $positional ) ); @@ -91,17 +88,17 @@ public function unknown_positionals( $args ) { */ public function validate_assoc( $assoc_args ) { $assoc_spec = $this->query_spec( - [ + array( 'type' => 'assoc', - ] + ) ); - $errors = [ - 'fatal' => [], - 'warning' => [], - ]; + $errors = array( + 'fatal' => array(), + 'warning' => array(), + ); - $to_unset = []; + $to_unset = array(); foreach ( $assoc_spec as $param ) { $key = $param['name']; @@ -110,38 +107,40 @@ public function validate_assoc( $assoc_args ) { if ( ! $param['optional'] ) { $errors['fatal'][ $key ] = "missing --$key parameter"; } - } elseif ( true === $assoc_args[ $key ] && ! $param['value']['optional'] ) { - $error_type = ( ! $param['optional'] ) ? 'fatal' : 'warning'; + } else { + if ( true === $assoc_args[ $key ] && ! $param['value']['optional'] ) { + $error_type = ( ! $param['optional'] ) ? 'fatal' : 'warning'; $errors[ $error_type ][ $key ] = "--$key parameter needs a value"; $to_unset[] = $key; + } } } - return [ $errors, $to_unset ]; + return array( $errors, $to_unset ); } /** * Check whether there are unknown parameters supplied. * * @param array $assoc_args Parameters passed to command. - * @return array + * @return array|false */ public function unknown_assoc( $assoc_args ) { $generic = $this->query_spec( - [ + array( 'type' => 'generic', - ] + ) ); if ( count( $generic ) ) { - return []; + return array(); } - $known_assoc = []; + $known_assoc = array(); foreach ( $this->spec as $param ) { - if ( in_array( $param['type'], [ 'assoc', 'flag' ], true ) ) { + if ( in_array( $param['type'], array( 'assoc', 'flag' ) ) ) { $known_assoc[] = $param['name']; } } @@ -158,24 +157,25 @@ public function unknown_assoc( $assoc_args ) { */ private function query_spec( $args, $operator = 'AND' ) { $operator = strtoupper( $operator ); - $count = count( $args ); - $filtered = []; + $count = count( $args ); + $filtered = array(); foreach ( $this->spec as $key => $to_match ) { $matched = 0; foreach ( $args as $m_key => $m_value ) { - if ( array_key_exists( $m_key, $to_match ) && $m_value === $to_match[ $m_key ] ) { - ++$matched; + if ( array_key_exists( $m_key, $to_match ) && $m_value == $to_match[ $m_key ] ) { + $matched++; } } - if ( ( 'AND' === $operator && $matched === $count ) - || ( 'OR' === $operator && $matched > 0 ) - || ( 'NOT' === $operator && 0 === $matched ) ) { + if ( ( 'AND' == $operator && $matched == $count ) + || ( 'OR' == $operator && $matched > 0 ) + || ( 'NOT' == $operator && 0 == $matched ) ) { $filtered[ $key ] = $to_match; } } return $filtered; } + } diff --git a/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php b/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php deleted file mode 100644 index befe98c8e..000000000 --- a/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php +++ /dev/null @@ -1,226 +0,0 @@ - The parent instance of the traverser. - */ - protected $parent; - - /** - * RecursiveDataStructureTraverser constructor. - * - * @param TData $data The data to read/manipulate by reference. - * @param string|int|null $key The key/property the data belongs to. - * @param self|null $parent_instance The parent instance of the traverser. - */ - public function __construct( &$data, $key = null, $parent_instance = null ) { - $this->data =& $data; - $this->key = $key; - $this->parent = $parent_instance; - } - - /** - * Get the nested value at the given key path. - * - * @param string|int|array $key_path - * - * @return mixed - */ - public function get( $key_path ) { - return $this->traverse_to( (array) $key_path )->value(); - } - - /** - * Get the current data. - * - * @return TData - */ - public function value() { - return $this->data; - } - - /** - * Update a nested value at the given key path. - * - * @param string|int|array $key_path - * @param mixed $value - * - * @return void - */ - public function update( $key_path, $value ) { - $this->traverse_to( (array) $key_path )->set_value( $value ); - } - - /** - * Update the current data with the given value. - * - * This will mutate the variable which was passed into the constructor - * as the data is set and traversed by reference. - * - * @param mixed $value - * - * @return void - */ - public function set_value( $value ) { - /** @var TData $value - We assume the new value matches the template or the template is mixed */ - $this->data = $value; - } - - /** - * Unset the value at the given key path. - * - * @param string|int|array $key_path - * - * @return void - */ - public function delete( $key_path ) { - $this->traverse_to( (array) $key_path )->unset_on_parent(); - } - - /** - * Define a nested value while creating keys if they do not exist. - * - * @param array $key_path - * @param mixed $value - * - * @return void - */ - public function insert( $key_path, $value ) { - try { - $this->update( $key_path, $value ); - } catch ( NonExistentKeyException $exception ) { - $exception->get_traverser()->create_key(); - $this->insert( $key_path, $value ); - } - } - - /** - * Delete the key on the parent's data that references this data. - * - * @return void - */ - public function unset_on_parent() { - if ( $this->parent && null !== $this->key ) { - $this->parent->delete_by_key( $this->key ); - } - } - - /** - * Delete the given key from the data. - * - * @param string|int $key - * - * @return void - */ - public function delete_by_key( $key ) { - if ( is_array( $this->data ) ) { - unset( $this->data[ $key ] ); - } elseif ( is_object( $this->data ) ) { - unset( $this->data->$key ); - } - } - - /** - * Get an instance of the traverser for the given hierarchical key. - * - * @param array $key_path Hierarchical key path within the current data to traverse to. - * - * @throws NonExistentKeyException - * - * @return self - */ - public function traverse_to( array $key_path ) { - $current = array_shift( $key_path ); - - if ( null === $current ) { - return $this; - } - - if ( ! $this->exists( $current ) ) { - $exception = new NonExistentKeyException( "No data exists for key \"{$current}\"" ); - // When throwing exception, we create a new traverser on the CURRENT level data - $exception->set_traverser( new self( $this->data, $current, $this->parent ) ); - throw $exception; - } - - /** - * We capture the array by reference. - */ - $data = &$this->data; - - if ( is_array( $data ) ) { - foreach ( $data as $key => &$key_data ) { - if ( $key === $current ) { - $traverser = new self( $key_data, $key, $this ); - return $traverser->traverse_to( $key_path ); - } - } - } elseif ( is_object( $data ) ) { - // Objects are passed by identifier, but to maintain the traverser logic - // specifically for scalar props on objects, we access them directly. - // Note: Traversing object properties by reference is tricky in PHP loops. - // We assume standard property access here. - if ( property_exists( $data, (string) $current ) ) { - // PHP Objects properties accessed like this are references if the object is passed. - $traverser = new self( $data->$current, $current, $this ); - return $traverser->traverse_to( $key_path ); - } - } - - // Should be unreachable due to exists() check, but static analysis likes certainty. - throw new NonExistentKeyException( 'Key path broken unexpectedly.' ); - } - - /** - * Create the key on the current data. - * - * @throws UnexpectedValueException - * @return void - */ - protected function create_key() { - $key = $this->key; - if ( is_array( $this->data ) && ( is_string( $key ) || is_int( $key ) ) ) { - $this->data[ $key ] = null; - } elseif ( is_object( $this->data ) && ( is_string( $key ) || is_int( $key ) ) ) { - $this->data->{$key} = null; - } else { - $type = gettype( $this->data ); - throw new UnexpectedValueException( - "Cannot create key \"{$this->key}\" on data type {$type}" - ); - } - } - - /** - * Check if the given key exists on the current data. - * - * @param string|int $key - * - * @return bool - */ - public function exists( $key ) { - return ( is_array( $this->data ) && array_key_exists( $key, $this->data ) ) || - ( is_object( $this->data ) && property_exists( $this->data, (string) $key ) ); - } -} diff --git a/php/WP_CLI/UpgraderSkin.php b/php/WP_CLI/UpgraderSkin.php index d3c787357..f9e0deaa0 100644 --- a/php/WP_CLI/UpgraderSkin.php +++ b/php/WP_CLI/UpgraderSkin.php @@ -2,15 +2,12 @@ namespace WP_CLI; -use WP_CLI; -use WP_Upgrader_Skin; - /** * A Upgrader Skin for WordPress that only generates plain-text * * @package wp-cli */ -class UpgraderSkin extends WP_Upgrader_Skin { +class UpgraderSkin extends \WP_Upgrader_Skin { public $api; @@ -19,13 +16,6 @@ public function footer() {} public function bulk_header() {} public function bulk_footer() {} - /** - * Show error message. - * - * @param string|\WP_Error $error Error message. - * - * @return void - */ public function error( $error ) { if ( ! $error ) { return; @@ -36,49 +26,35 @@ public function error( $error ) { } // TODO: show all errors, not just the first one - WP_CLI::warning( $error ); - } - - /** - * @param string $string - * @param mixed ...$args Optional text replacements. - */ - public function feedback( $string, ...$args ) { - $args_array = []; - foreach ( $args as $arg ) { - $args_array[] = $args; - } - - $this->process_feedback( $string, $args ); + \WP_CLI::warning( $error ); } - /** - * Process the feedback collected through the compat indirection. - * - * @param string $string String to use as feedback message. - * @param array $args Array of additional arguments to process. - */ - public function process_feedback( $string, $args ) { + public function feedback( $string ) { if ( 'parent_theme_prepare_install' === $string ) { - WP_CLI::get_http_cache_manager()->whitelist_package( $this->api->download_link, 'theme', $this->api->slug, $this->api->version ); + \WP_CLI::get_http_cache_manager()->whitelist_package( $this->api->download_link, 'theme', $this->api->slug, $this->api->version ); } if ( isset( $this->upgrader->strings[ $string ] ) ) { $string = $this->upgrader->strings[ $string ]; } - if ( ! empty( $args ) && strpos( $string, '%' ) !== false ) { - $string = vsprintf( $string, $args ); + if ( strpos( $string, '%' ) !== false ) { + $args = func_get_args(); + $args = array_splice( $args, 1 ); + if ( ! empty( $args ) ) { + $string = vsprintf( $string, $args ); + } } if ( empty( $string ) ) { return; } - $string = str_replace( '…', '...', Utils\strip_tags( $string ) ); + $string = str_replace( '…', '...', strip_tags( $string ) ); $string = html_entity_decode( $string, ENT_QUOTES, get_bloginfo( 'charset' ) ); - WP_CLI::log( $string ); + \WP_CLI::log( $string ); } } + diff --git a/php/WP_CLI/WpHttpCacheManager.php b/php/WP_CLI/WpHttpCacheManager.php index a88890b82..4fff0aa86 100644 --- a/php/WP_CLI/WpHttpCacheManager.php +++ b/php/WP_CLI/WpHttpCacheManager.php @@ -1,9 +1,10 @@ map whitelisted urls to keys and ttls + * @var array map whitelisted urls to keys and ttls */ - protected $whitelist = []; + protected $whitelist = array(); /** * @var FileCache */ protected $cache; - /** - * Minimum valid archive file size in bytes. - * - * This threshold (20 bytes) roughly corresponds to the smallest possible - * valid ZIP or TAR.GZ header, ensuring we skip obviously invalid or empty downloads. - */ - private const MIN_VALID_ARCHIVE_SIZE = 20; - /** * @param FileCache $cache */ @@ -36,8 +29,8 @@ public function __construct( FileCache $cache ) { $this->cache = $cache; // hook into wp http api - add_filter( 'pre_http_request', [ $this, 'filter_pre_http_request' ], 10, 3 ); - add_filter( 'http_response', [ $this, 'filter_http_response' ], 10, 3 ); + add_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 10, 3 ); + add_filter( 'http_response', array( $this, 'filter_http_response' ), 10, 3 ); } /** @@ -58,13 +51,13 @@ public function filter_pre_http_request( $response, $args, $url ) { WP_CLI::log( sprintf( 'Using cached file \'%s\'...', $filename ) ); if ( copy( $filename, $args['filename'] ) ) { // simulate successful download response - return [ - 'response' => [ - 'code' => 200, + return array( + 'response' => array( + 'code' => 200, 'message' => 'OK', - ], + ), 'filename' => $args['filename'], - ]; + ); } WP_CLI::error( sprintf( 'Error copying cached file %s to %s', $filename, $url ) ); @@ -76,10 +69,9 @@ public function filter_pre_http_request( $response, $args, $url ) { /** * cache wp http api downloads * - * @param array $response - * @param array $args + * @param array $response + * @param array $args * @param string $url - * @return array */ public function filter_http_response( $response, $args, $url ) { // check if whitelisted @@ -91,12 +83,7 @@ public function filter_http_response( $response, $args, $url ) { return $response; } // check if download was successful - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { - return $response; - } - // Validate before caching. - if ( ! $this->validate_downloaded_file( $response['filename'], $url ) ) { - WP_CLI::warning( "Invalid or corrupt file from {$url}, skipping cache." ); + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return $response; } // cache downloaded file @@ -104,55 +91,6 @@ public function filter_http_response( $response, $args, $url ) { return $response; } - /** - * Validate downloaded file before adding to cache. - * - * @param string $file Path to the downloaded file. - * @param string $url Source URL. - * @return bool True if file is valid, false otherwise. - */ - private function validate_downloaded_file( $file, $url ) { - if ( ! is_readable( $file ) ) { - return false; - } - - $size = filesize( $file ); - if ( false === $size || $size < self::MIN_VALID_ARCHIVE_SIZE ) { - return false; - } - - $ext = strtolower( pathinfo( (string) wp_parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION ) ); - $mime = function_exists( 'mime_content_type' ) ? mime_content_type( $file ) : ''; - - if ( ( 'zip' === $ext || 'application/zip' === $mime ) && class_exists( '\ZipArchive' ) ) { - $zip = new \ZipArchive(); - $result = $zip->open( $file ); - if ( true !== $result ) { - return false; - } - // Optional deeper check: ensure we can read file list. - if ( 0 === $zip->numFiles ) { //phpcs:ignore - $zip->close(); - return false; - } - $zip->close(); - } - - if ( ( preg_match( '/\.tar\.gz$/i', $url ) || 'application/gzip' === $mime ) && class_exists( '\PharData' ) ) { - try { - $phar = new \PharData( $file ); - // Accessing the file list ensures it can be read. - if ( empty( iterator_to_array( $phar ) ) ) { - return false; - } - } catch ( \Exception $e ) { - return false; - } - } - - return true; - } - /** * whitelist a package url * @@ -163,7 +101,7 @@ private function validate_downloaded_file( $file, $url ) { * @param int $ttl */ public function whitelist_package( $url, $group, $slug, $version, $ttl = null ) { - $ext = pathinfo( (string) Utils\parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION ); + $ext = pathinfo( parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION ); $key = "$group/$slug-$version.$ext"; $this->whitelist_url( $url, $key, $ttl ); wp_update_plugins(); @@ -177,11 +115,8 @@ public function whitelist_package( $url, $group, $slug, $version, $ttl = null ) * @param int $ttl */ public function whitelist_url( $url, $key = null, $ttl = null ) { - $key = $key ? : $url; - $this->whitelist[ $url ] = [ - 'key' => $key, - 'ttl' => $ttl, - ]; + $key = $key ? : $url; + $this->whitelist[ $url ] = compact( 'key', 'ttl' ); } /** @@ -193,4 +128,5 @@ public function whitelist_url( $url, $key = null, $ttl = null ) { public function is_whitelisted( $url ) { return isset( $this->whitelist[ $url ] ); } + } diff --git a/php/WP_CLI/WpOrgApi.php b/php/WP_CLI/WpOrgApi.php deleted file mode 100644 index e5c363805..000000000 --- a/php/WP_CLI/WpOrgApi.php +++ /dev/null @@ -1,352 +0,0 @@ -options = $options; - } - - /** - * Gets the checksums for the given version of WordPress core. - * - * @param string $version Version string to query. - * @param string $locale Optional. Locale to query. Defaults to 'en_US'. - * @return false|array False on failure. An array of checksums on success. - * @throws RuntimeException If the remote request fails. - */ - public function get_core_checksums( $version, $locale = 'en_US' ) { - $data = [ - 'version' => $version, - 'locale' => $locale, - ]; - - $url = sprintf( - '%s?%s', - self::CORE_CHECKSUMS_ENDPOINT, - http_build_query( $data, '', '&' ) - ); - - $response = $this->json_get_request( $url ); - - if ( - ! is_array( $response ) - || ! isset( $response['checksums'] ) - || ! is_array( $response['checksums'] ) - ) { - return false; - } - - return $response['checksums']; - } - - /** - * Gets a core version check. - * - * @param string $locale Optional. Locale to request a version check for. Defaults to 'en_US'. - * @return array|false False on failure. Associative array of the offer on success. - * @throws RuntimeException If the remote request failed. - */ - public function get_core_version_check( $locale = 'en_US' ) { - $url = sprintf( - '%s?%s', - self::VERSION_CHECK_ENDPOINT, - http_build_query( [ 'locale' => $locale ], '', '&' ) - ); - - $response = $this->json_get_request( $url ); - - if ( ! is_array( $response ) ) { - return false; - } - - return $response; - } - - /** - * Gets a download offer. - * - * @param string $locale Optional. Locale to request an offer from. Defaults to 'en_US'. - * @return array|false False on failure. Associative array of the offer on success. - * @throws RuntimeException If the remote request failed. - */ - public function get_core_download_offer( $locale = 'en_US' ) { - $response = $this->get_core_version_check( $locale ); - - if ( - ! is_array( $response ) - || ! isset( $response['offers'] ) - || ! is_array( $response['offers'] ) - ) { - return false; - } - - $offer = $response['offers'][0]; - - if ( ! is_array( $offer ) || ! array_key_exists( 'locale', $offer ) || $locale !== $offer['locale'] ) { - return false; - } - - return $offer; - } - - /** - * Gets the checksums for the given version of plugin. - * - * @param string $plugin Plugin slug to query. - * @param string $version Version string to query. - * @return false|array False on failure. An array of checksums on success. - * @throws RuntimeException If the remote request fails. - */ - public function get_plugin_checksums( $plugin, $version ) { - $url = sprintf( - '%s%s/%s.json', - self::PLUGIN_CHECKSUMS_ENDPOINT, - $plugin, - $version - ); - - $response = $this->json_get_request( $url ); - - if ( - ! is_array( $response ) - || ! isset( $response['files'] ) - || ! is_array( $response['files'] ) - ) { - return false; - } - - return $response['files']; - } - - /** - * Gets a plugin's info. - * - * @param string $plugin Plugin slug to query. - * @param string $locale Optional. Locale to request info for. Defaults to 'en_US'. - * @param array $fields Optional. Fields to include/omit from the response. - * @return array|false False on failure. Associative array of the offer on success. - * @throws RuntimeException If the remote request failed. - */ - public function get_plugin_info( $plugin, $locale = 'en_US', array $fields = [] ) { - $action = 'plugin_information'; - $request = [ - 'locale' => $locale, - 'slug' => $plugin, - ]; - - if ( ! empty( $fields ) ) { - $request['fields'] = $fields; - } - - $url = sprintf( - '%s?%s', - self::PLUGIN_INFO_ENDPOINT, - http_build_query( compact( 'action', 'request' ), '', '&' ) - ); - - $response = $this->json_get_request( $url ); - - if ( ! is_array( $response ) ) { - return false; - } - - return $response; - } - - /** - * Gets a theme's info. - * - * @param string $theme Theme slug to query. - * @param string $locale Optional. Locale to request info for. Defaults to 'en_US'. - * @param array $fields Optional. Fields to include/omit from the response. - * @return array|false False on failure. Associative array of the offer on success. - * @throws RuntimeException If the remote request failed. - */ - public function get_theme_info( $theme, $locale = 'en_US', array $fields = [] ) { - $action = 'theme_information'; - $request = [ - 'locale' => $locale, - 'slug' => $theme, - ]; - - if ( ! empty( $fields ) ) { - $request['fields'] = $fields; - } - - $url = sprintf( - '%s?%s', - self::THEME_INFO_ENDPOINT, - http_build_query( compact( 'action', 'request' ), '', '&' ) - ); - - $response = $this->json_get_request( $url ); - - if ( ! is_array( $response ) ) { - return false; - } - - return $response; - } - - /** - * Gets a set of salts in the format required by `wp-config.php`. - * - * @return string A string of PHP define() statements. - * @throws RuntimeException If the remote request fails. - */ - public function get_salts() { - return $this->get_request( self::SALT_ENDPOINT ); - } - - /** - * Execute a remote GET request. - * - * @param string $url URL to execute the GET request on. - * @param array $headers Optional. Associative array of headers. - * @param array $options Optional. Associative array of options. - * @return mixed|false False on failure. Decoded JSON on success. - * @throws RuntimeException If the JSON could not be decoded. - */ - private function json_get_request( $url, $headers = [], $options = [] ) { - $headers = array_merge( - [ - 'Accept' => 'application/json', - ], - $headers - ); - - $response = $this->get_request( $url, $headers, $options ); - - if ( false === $response ) { - return $response; - } - - $data = json_decode( $response, true ); - - if ( JSON_ERROR_NONE !== json_last_error() ) { - throw new RuntimeException( 'Failed to decode JSON: ' . json_last_error_msg() ); - } - - return $data; - } - - /** - * Execute a remote GET request. - * - * @param string $url URL to execute the GET request on. - * @param array $headers Optional. Associative array of headers. - * @param array $options Optional. Associative array of options. - * @return string Response body. - * @throws RuntimeException If the remote request fails. - */ - private function get_request( $url, $headers = [], $options = [] ) { - $options = array_merge( - $this->options, - [ - 'halt_on_error' => false, - ], - $options - ); - - $response = Utils\http_request( 'GET', $url, null, $headers, $options ); - - if ( - ! $response->success - || 200 > (int) $response->status_code - || 300 <= $response->status_code - ) { - throw new RuntimeException( - "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." - ); - } - - return trim( $response->body ); - } -} diff --git a/php/boot-fs.php b/php/boot-fs.php index 726f2c0c0..f1cfc5900 100644 --- a/php/boot-fs.php +++ b/php/boot-fs.php @@ -7,11 +7,12 @@ die( -1 ); } -if ( version_compare( PHP_VERSION, '7.2.24', '<' ) ) { - printf( "Error: WP-CLI requires PHP %s or newer. You are running version %s.\n", '7.2.24', PHP_VERSION ); +if ( version_compare( PHP_VERSION, '5.3.0', '<' ) ) { + printf( "Error: WP-CLI requires PHP %s or newer. You are running version %s.\n", '5.3.0', PHP_VERSION ); die( -1 ); } define( 'WP_CLI_ROOT', dirname( __DIR__ ) ); -require_once WP_CLI_ROOT . '/php/wp-cli.php'; +include_once WP_CLI_ROOT . '/php/wp-cli.php'; + diff --git a/php/boot-phar.php b/php/boot-phar.php new file mode 100644 index 000000000..946e8369e --- /dev/null +++ b/php/boot-phar.php @@ -0,0 +1,15 @@ +process( $state ); + $state = $step_instance->process( $state ); } } diff --git a/php/class-wp-cli-command.php b/php/class-wp-cli-command.php index ed76f8838..0cccc51a4 100644 --- a/php/class-wp-cli-command.php +++ b/php/class-wp-cli-command.php @@ -7,8 +7,6 @@ */ abstract class WP_CLI_Command { - /** - * Instantiate a new WP_CLI_Command. - */ public function __construct() {} } + diff --git a/php/class-wp-cli.php b/php/class-wp-cli.php index 627303408..18cefd9be 100644 --- a/php/class-wp-cli.php +++ b/php/class-wp-cli.php @@ -1,99 +1,57 @@ in_color() ); + return \cli\Colors::colorize( $string, self::get_runner()->in_color() ); } /** @@ -250,19 +205,13 @@ public static function colorize( $string ) { * * * `before_add_command:` - Before the command is added. * * `after_add_command:` - After the command was added. - * * `before_invoke:` (1) - Just before a command is invoked. - * * `after_invoke:` (1) - Just after a command is invoked. + * * `before_invoke:` - Just before a command is invoked. + * * `after_invoke:` - Just after a command is invoked. * * `find_command_to_run_pre` - Just before WP-CLI finds the command to run. - * * `before_registering_contexts` (1) - Before the contexts are registered. * * `before_wp_load` - Just before the WP load process begins. * * `before_wp_config_load` - After wp-config.php has been located. * * `after_wp_config_load` - After wp-config.php has been loaded into scope. * * `after_wp_load` - Just after the WP load process has completed. - * * `before_run_command` (3) - Just before the command is executed. - * - * The parentheses behind the hook name denote the number of arguments - * being passed into the hook. For such hooks, the callback should return - * the first argument again, making them work like a WP filter. * * WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`. * @@ -272,9 +221,9 @@ public static function colorize( $string ) { * ``` * # `wp network meta` confirms command is executing in multisite context. * WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array( - * 'before_invoke' => function ( $name ) { + * 'before_invoke' => function () { * if ( !is_multisite() ) { - * WP_CLI::error( 'This is not a multisite installation.' ); + * WP_CLI::error( 'This is not a multisite install.' ); * } * } * ) ); @@ -283,20 +232,12 @@ public static function colorize( $string ) { * @access public * @category Registration * - * @param string $when Identifier for the hook. - * @param callable $callback Callback to execute when hook is called. - * @return void + * @param string $when Identifier for the hook. + * @param mixed $callback Callback to execute when hook is called. + * @return null */ public static function add_hook( $when, $callback ) { if ( array_key_exists( $when, self::$hooks_passed ) ) { - self::debug( - sprintf( - 'Immediately invoking on passed hook "%s": %s', - $when, - Utils\describe_callable( $callback ) - ), - 'hooks' - ); call_user_func_array( $callback, (array) self::$hooks_passed[ $when ] ); } @@ -312,59 +253,25 @@ public static function add_hook( $when, $callback ) { * @access public * @category Registration * - * @param string $when Identifier for the hook. - * @param mixed ...$args Optional. Arguments that will be passed onto the - * callback provided by `WP_CLI::add_hook()`. - * @return null|mixed Returns the first optional argument if optional - * arguments were passed, otherwise returns null. + * @param string $when Identifier for the hook. + * @param mixed ... Optional. Arguments that will be passed onto the + * callback provided by `WP_CLI::add_hook()`. + * @return null */ - public static function do_hook( $when, ...$args ) { - self::$hooks_passed[ $when ] = $args; + public static function do_hook( $when ) { + $args = func_num_args() > 1 + ? array_slice( func_get_args(), 1 ) + : array(); - $has_args = count( $args ) > 0; + self::$hooks_passed[ $when ] = $args; if ( ! isset( self::$hooks[ $when ] ) ) { - if ( $has_args ) { - return $args[0]; - } - - return null; + return; } - self::debug( - sprintf( - 'Processing hook "%s" with %d callbacks', - $when, - count( self::$hooks[ $when ] ) - ), - 'hooks' - ); - foreach ( self::$hooks[ $when ] as $callback ) { - self::debug( - sprintf( - 'On hook "%s": %s', - $when, - Utils\describe_callable( $callback ) - ), - 'hooks' - ); - - if ( $has_args ) { - $return_value = $callback( ...$args ); - if ( isset( $return_value ) ) { - $args[0] = $return_value; - } - } else { - $callback(); - } + call_user_func_array( $callback, $args ); } - - if ( $has_args ) { - return $args[0]; - } - - return null; } /** @@ -377,10 +284,10 @@ public static function do_hook( $when, ...$args ) { * @access public * @category Registration * - * @param string $tag Named WordPress action or filter. - * @param callable $function_to_add Callable to execute when the action or filter is evaluated. - * @param integer $priority Priority to add the callback as. - * @param integer $accepted_args Number of arguments to pass to callback. + * @param string $tag Named WordPress action or filter. + * @param mixed $function_to_add Callable to execute when the action or filter is evaluated. + * @param integer $priority Priority to add the callback as. + * @param integer $accepted_args Number of arguments to pass to callback. * @return true */ public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { @@ -390,12 +297,10 @@ public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $acc add_filter( $tag, $function_to_add, $priority, $accepted_args ); } else { $idx = self::wp_hook_build_unique_id( $tag, $function_to_add, $priority ); - - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- This is intentional & the purpose of this function. - $wp_filter[ $tag ][ $priority ][ $idx ] = [ - 'function' => $function_to_add, + $wp_filter[ $tag ][ $priority ][ $idx ] = array( + 'function' => $function_to_add, 'accepted_args' => $accepted_args, - ]; + ); unset( $merged_filters[ $tag ] ); } @@ -416,14 +321,14 @@ private static function wp_hook_build_unique_id( $tag, $function, $priority ) { } if ( is_object( $function ) ) { - // Closures are currently implemented as objects. - $function = [ $function, '' ]; + // Closures are currently implemented as objects + $function = array( $function, '' ); } else { $function = (array) $function; } if ( is_object( $function[0] ) ) { - // Object Class Calling. + // Object Class Calling if ( function_exists( 'spl_object_hash' ) ) { return spl_object_hash( $function[0] ) . $function[1]; } @@ -434,8 +339,6 @@ private static function wp_hook_build_unique_id( $tag, $function, $priority ) { return false; } $obj_idx .= isset( $wp_filter[ $tag ][ $priority ] ) ? count( (array) $wp_filter[ $tag ][ $priority ] ) : $filter_id_count; - - // @phpstan-ignore property.notFound $function[0]->wp_filter_id = $filter_id_count; ++$filter_id_count; } else { @@ -446,7 +349,7 @@ private static function wp_hook_build_unique_id( $tag, $function, $priority ) { } if ( is_string( $function[0] ) ) { - // Static Calling. + // Static Calling return $function[0] . '::' . $function[1]; } } @@ -487,9 +390,9 @@ private static function wp_hook_build_unique_id( $tag, $function, $priority ) { * @access public * @category Registration * - * @param string $name Name for the command (e.g. "post list" or "site empty"). - * @param callable|object|string|string[] $callable Command implementation as a class, function or closure. - * @param array $args { + * @param string $name Name for the command (e.g. "post list" or "site empty"). + * @param callable $callable Command implementation as a class, function or closure. + * @param array $args { * Optional. An associative array with additional registration parameters. * * @type callable $before_invoke Callback to execute before invoking the command. @@ -500,11 +403,9 @@ private static function wp_hook_build_unique_id( $tag, $function, $priority ) { * @type string $when Execute callback on a named WP-CLI hook (e.g. before_wp_load). * @type bool $is_deferred Whether the command addition had already been deferred. * } - * @return bool True on success, false if deferred, hard error if registration failed. - * - * @phpstan-param array{before_invoke?: callable, after_invoke?: callable, shortdesc?: string, longdesc?: string, synopsis?: string|CommandSynopsis[], when?: string, is_deferred?: bool} $args + * @return true True on success, false if deferred, hard error if registration failed. */ - public static function add_command( $name, $callable, $args = [] ) { + public static function add_command( $name, $callable, $args = array() ) { // Bail immediately if the WP-CLI executable has not been run. if ( ! defined( 'WP_CLI' ) ) { return false; @@ -513,68 +414,56 @@ public static function add_command( $name, $callable, $args = [] ) { $valid = false; if ( is_callable( $callable ) ) { $valid = true; - } elseif ( is_string( $callable ) && class_exists( $callable ) ) { + } elseif ( is_string( $callable ) && class_exists( (string) $callable ) ) { $valid = true; } elseif ( is_object( $callable ) ) { $valid = true; - } elseif ( is_array( $callable ) && Utils\is_valid_class_and_method_pair( $callable ) ) { - $valid = true; } if ( ! $valid ) { if ( is_array( $callable ) ) { $callable[0] = is_object( $callable[0] ) ? get_class( $callable[0] ) : $callable[0]; - $callable = [ $callable[0], $callable[1] ]; + $callable = array( $callable[0], $callable[1] ); } - self::error( sprintf( 'Callable %s does not exist, and cannot be registered as `wp %s`.', json_encode( $callable ), $name ) ); + WP_CLI::error( sprintf( 'Callable %s does not exist, and cannot be registered as `wp %s`.', json_encode( $callable ), $name ) ); } - $addition = new CommandAddition(); + $addition = new Dispatcher\CommandAddition(); self::do_hook( "before_add_command:{$name}", $addition ); if ( $addition->was_aborted() ) { - self::warning( "Aborting the addition of the command '{$name}' with reason: {$addition->get_reason()}." ); + WP_CLI::warning( "Aborting the addition of the command '{$name}' with reason: {$addition->get_reason()}." ); + return false; } - foreach ( [ 'before_invoke', 'after_invoke' ] as $when ) { + foreach ( array( 'before_invoke', 'after_invoke' ) as $when ) { if ( isset( $args[ $when ] ) ) { self::add_hook( "{$when}:{$name}", $args[ $when ] ); } } - $path = preg_split( '/\s+/', $name ) ?: []; + $path = preg_split( '/\s+/', $name ); - $leaf_name = (string) array_pop( $path ); + $leaf_name = array_pop( $path ); + $full_path = $path; $command = self::get_root_command(); while ( ! empty( $path ) ) { $subcommand_name = $path[0]; - $parent = implode( ' ', $path ); - $subcommand = $command->find_subcommand( $path ); + $parent = implode( ' ', $path ); + $subcommand = $command->find_subcommand( $path ); // Parent not found. Defer addition or create an empty container as // needed. if ( ! $subcommand ) { if ( isset( $args['is_deferred'] ) && $args['is_deferred'] ) { - $subcommand = new CompositeCommand( + $subcommand = new Dispatcher\CompositeCommand( $command, $subcommand_name, - new DocParser( '' ) + new \WP_CLI\DocParser( '' ) ); - - self::debug( - "Adding empty container for deferred command: {$name}", - 'commands' - ); - $command->add_subcommand( $subcommand_name, $subcommand ); } else { - self::debug( "Deferring command: {$name}", 'commands' ); - - /** - * @var callable $callable - */ - self::defer_command_addition( $name, $parent, @@ -589,44 +478,21 @@ public static function add_command( $name, $callable, $args = [] ) { $command = $subcommand; } - $leaf_command = CommandFactory::create( $leaf_name, $callable, $command ); - - if ( $addition->was_aborted() ) { - $leaf_command = new DisabledCommand( $command, $leaf_name, $leaf_command->get_docparser(), $addition->get_reason() ); - } + $leaf_command = Dispatcher\CommandFactory::create( $leaf_name, $callable, $command ); - // Only add a command namespace if the command itself does not exist yet. - if ( $leaf_command instanceof CommandNamespace - && array_key_exists( $leaf_name, $command->get_subcommands() ) ) { + if ( $leaf_command instanceof Dispatcher\CommandNamespace && array_key_exists( $leaf_name, $command->get_subcommands() ) ) { return false; } - // Reattach commands attached to namespace to real command. - $subcommand_name = (array) $leaf_name; - $existing_command = $command->find_subcommand( $subcommand_name ); - if ( $existing_command instanceof CompositeCommand && $existing_command->can_have_subcommands() ) { - if ( $leaf_command instanceof CommandNamespace || ! $leaf_command->can_have_subcommands() ) { - $command_to_keep = $existing_command; - } else { - $command_to_keep = $leaf_command; - } - - self::merge_sub_commands( $command_to_keep, $existing_command, $leaf_command ); - } - - /** @var Dispatcher\Subcommand|Dispatcher\CompositeCommand|Dispatcher\CommandNamespace $leaf_command */ - if ( ! $command->can_have_subcommands() ) { throw new Exception( sprintf( "'%s' can't have subcommands.", - implode( ' ', Dispatcher\get_path( $command ) ) + implode( ' ' , Dispatcher\get_path( $command ) ) ) ); } - /** @var Dispatcher\Subcommand $leaf_command */ - if ( isset( $args['shortdesc'] ) ) { $leaf_command->set_shortdesc( $args['shortdesc'] ); } @@ -639,33 +505,30 @@ public static function add_command( $name, $callable, $args = [] ) { if ( is_string( $args['synopsis'] ) ) { $leaf_command->set_synopsis( $args['synopsis'] ); } elseif ( is_array( $args['synopsis'] ) ) { - $synopsis = SynopsisParser::render( $args['synopsis'] ); + $synopsis = \WP_CLI\SynopsisParser::render( $args['synopsis'] ); $leaf_command->set_synopsis( $synopsis ); $long_desc = ''; - $bits = explode( ' ', $synopsis ); + $bits = explode( ' ', $synopsis ); foreach ( $args['synopsis'] as $key => $arg ) { - $long_desc .= $bits[ $key ] . "\n"; + $long_desc .= $bits[ $key ] . PHP_EOL; if ( ! empty( $arg['description'] ) ) { - $long_desc .= ': ' . $arg['description'] . "\n"; + $long_desc .= ': ' . $arg['description'] . PHP_EOL; } - $yamlify = []; - foreach ( [ 'default', 'options' ] as $_key ) { - if ( isset( $arg[ $_key ] ) ) { - $yamlify[ $_key ] = $arg[ $_key ]; + $yamlify = array(); + foreach ( array( 'default', 'options' ) as $key ) { + if ( isset( $arg[ $key ] ) ) { + $yamlify[ $key ] = $arg[ $key ]; } } if ( ! empty( $yamlify ) ) { $long_desc .= Spyc::YAMLDump( $yamlify ); - $long_desc .= '---' . "\n"; + $long_desc .= '---' . PHP_EOL; } - $long_desc .= "\n"; + $long_desc .= PHP_EOL; } if ( ! empty( $long_desc ) ) { - $long_desc = rtrim( $long_desc, "\r\n" ); - $long_desc = '## OPTIONS' . "\n\n" . $long_desc; - if ( ! empty( $args['longdesc'] ) ) { - $long_desc .= "\n\n" . ltrim( $args['longdesc'], "\r\n" ); - } + $long_desc = rtrim( $long_desc, PHP_EOL ); + $long_desc = '## OPTIONS' . PHP_EOL . PHP_EOL . $long_desc; $leaf_command->set_longdesc( $long_desc ); } } @@ -675,58 +538,28 @@ public static function add_command( $name, $callable, $args = [] ) { self::get_runner()->register_early_invoke( $args['when'], $leaf_command ); } - $command_type = $leaf_command instanceof CommandNamespace ? 'namespace' : 'command'; - if ( ! empty( $parent ) ) { - $sub_command = trim( str_replace( $parent, '', $name ) ); - self::debug( "Adding {$command_type}: {$sub_command} in {$parent} Namespace", 'commands' ); - } else { - self::debug( "Adding {$command_type}: {$name}", 'commands' ); - } - $command->add_subcommand( $leaf_name, $leaf_command ); self::do_hook( "after_add_command:{$name}" ); return true; } - /** - * Merge the sub-commands of two commands into a single command to keep. - * - * @param CompositeCommand $command_to_keep Command to merge the sub commands into. This is typically one of the - * two others. - * @param CompositeCommand $old_command Command that was already registered. - * @param CompositeCommand $new_command New command that is being added. - */ - private static function merge_sub_commands( - CompositeCommand $command_to_keep, - CompositeCommand $old_command, - CompositeCommand $new_command - ) { - foreach ( $old_command->get_subcommands() as $subname => $subcommand ) { - $command_to_keep->add_subcommand( $subname, $subcommand, false ); - } - - foreach ( $new_command->get_subcommands() as $subname => $subcommand ) { - $command_to_keep->add_subcommand( $subname, $subcommand, true ); - } - } - /** * Defer command addition for a sub-command if the parent command is not yet * registered. * - * @param string $name Name for the sub-command. - * @param string $parent Name for the parent command. - * @param callable $callable Command implementation as a class, function or closure. - * @param array $args Optional. See `WP_CLI::add_command()` for details. + * @param string $name Name for the sub-command. + * @param string $parent Name for the parent command. + * @param string $callable Command implementation as a class, function or closure. + * @param array $args Optional. See `WP_CLI::add_command()` for details. */ - private static function defer_command_addition( $name, $parent, $callable, $args = [] ) { - $args['is_deferred'] = true; - self::$deferred_additions[ $name ] = [ + private static function defer_command_addition( $name, $parent, $callable, $args = array() ) { + $args['is_deferred'] = true; + self::$deferred_additions[ $name ] = array( 'parent' => $parent, 'callable' => $callable, 'args' => $args, - ]; + ); self::add_hook( "after_add_command:$parent", function () use ( $name ) { @@ -759,93 +592,12 @@ public static function get_deferred_additions() { */ public static function remove_deferred_addition( $name ) { if ( ! array_key_exists( $name, self::$deferred_additions ) ) { - self::warning( "Trying to remove a non-existent command addition '{$name}'." ); + WP_CLI::warning( "Trying to remove a non-existent command addition '{$name}'." ); } unset( self::$deferred_additions[ $name ] ); } - /** - * Check if a command's arguments conflict with global arguments. - * - * Issues warnings for any command arguments that have the same name as - * global WP-CLI arguments (e.g., --debug, --user, --quiet). - * - * @param string $command_name The name of the command being registered. - * @param Dispatcher\Subcommand $command The command object to check. - */ - public static function check_global_arg_conflicts( $command_name, $command ) { - $synopsis = $command->get_synopsis(); - if ( ! $synopsis ) { - return; - } - - // Check if command has opted out of this check - if ( self::command_skips_global_arg_check( $command ) ) { - return; - } - - // Get global argument names from config spec (cached) - if ( null === self::$global_arg_names ) { - self::$global_arg_names = []; - foreach ( self::get_configurator()->get_spec() as $key => $details ) { - if ( false === $details['runtime'] ) { - continue; - } - if ( isset( $details['deprecated'] ) ) { - continue; - } - if ( isset( $details['hidden'] ) ) { - continue; - } - self::$global_arg_names[] = $key; - } - } - - // Parse the command's synopsis to get its argument names - $synopsis_params = SynopsisParser::parse( $synopsis ); - $conflicts = []; - - foreach ( $synopsis_params as $param ) { - // Check assoc and flag types; generic type has no specific name to conflict - if ( in_array( $param['type'], [ 'assoc', 'flag' ], true ) && isset( $param['name'] ) ) { - if ( in_array( $param['name'], self::$global_arg_names, true ) ) { - $conflicts[] = $param['name']; - } - } - } - - // Warn about any conflicts found - foreach ( $conflicts as $conflict ) { - self::warning( - sprintf( - "The `%s` command is registering an argument '--%s' that conflicts with a global argument of the same name.", - $command_name, - $conflict - ) - ); - } - } - - /** - * Check if a command has opted out of global argument conflict checking. - * - * Commands can use the @skipglobalargcheck tag in their PHPdoc to disable - * the warning for global argument conflicts. - * - * @param Dispatcher\Subcommand $command The command object to check. - * @return bool True if the command should skip the check, false otherwise. - */ - private static function command_skips_global_arg_check( $command ) { - $docparser = $command->get_docparser(); - - if ( ! $docparser ) { - return false; - } - - return $docparser->has_tag( 'skipglobalargcheck' ); - } - /** * Display informational message without prefix, and ignore `--quiet`. * @@ -856,11 +608,10 @@ private static function command_skips_global_arg_check( $command ) { * @category Output * * @param string $message Message to display to the end user. - * @param bool $newline Optional. Whether to append a newline to the end of the message. Default true. - * @return void + * @return null */ - public static function line( $message = '', $newline = true ) { - echo $message . ( $newline ? "\n" : '' ); + public static function line( $message = '' ) { + echo $message . "\n"; } /** @@ -877,20 +628,15 @@ public static function line( $message = '', $newline = true ) { * @category Output * * @param string $message Message to write to STDOUT. - * @param bool $newline Optional. Whether to append a newline to the end of the message. Default true. */ - public static function log( $message, $newline = true ) { - if ( null === self::$logger ) { - return; - } - - self::$logger->info( $message, $newline ); + public static function log( $message ) { + self::$logger->info( $message ); } /** * Display success message prefixed with "Success: ". * - * Success message is written to STDOUT, or discarded when `--quiet` flag is supplied. + * Success message is written to STDOUT. * * Typically recommended to inform user of successful script conclusion. * @@ -908,13 +654,9 @@ public static function log( $message, $newline = true ) { * @category Output * * @param string $message Message to write to STDOUT. - * @return void + * @return null */ public static function success( $message ) { - if ( null === self::$logger ) { - return; - } - self::$logger->success( $message ); } @@ -943,34 +685,18 @@ public static function success( $message ) { * @access public * @category Output * - * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. - * @param string|bool $group Organize debug message to a specific group. - * Use `false` to not group the message. - * @return void + * @param string $message Message to write to STDERR. + * @param string $group Organize debug message to a specific group. + * @return null */ public static function debug( $message, $group = false ) { - static $storage = []; - - if ( ! self::$logger ) { - $storage[] = [ $message, $group ]; - return; - } - - if ( ! empty( $storage ) ) { - foreach ( $storage as $entry ) { - list( $stored_message, $stored_group ) = $entry; - self::$logger->debug( self::error_to_string( $stored_message ), $stored_group ); - } - $storage = []; - } - self::$logger->debug( self::error_to_string( $message ), $group ); } /** * Display warning message prefixed with "Warning: ". * - * Warning message is written to STDERR, or discarded when `--quiet` flag is supplied. + * Warning message is written to STDERR. * * Use instead of `WP_CLI::debug()` when script execution should be permitted * to continue. @@ -988,14 +714,10 @@ public static function debug( $message, $group = false ) { * @access public * @category Output * - * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. - * @return void + * @param string $message Message to write to STDERR. + * @return null */ public static function warning( $message ) { - if ( null === self::$logger ) { - return; - } - self::$logger->warning( self::error_to_string( $message ) ); } @@ -1008,10 +730,6 @@ public static function warning( $message ) { * Use `WP_CLI::warning()` instead when script execution should be permitted * to continue. * - * When `--debug` is enabled, this method will also output a backtrace - * showing where the error was triggered from, making it easier to identify - * problematic code. - * * ``` * # `wp cache flush` considers flush failure to be a fatal error. * if ( false === wp_cache_flush() ) { @@ -1022,14 +740,12 @@ public static function warning( $message ) { * @access public * @category Output * - * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. - * @param boolean|int $exit True defaults to exit(1). + * @param string|WP_Error $message Message to write to STDERR. + * @param boolean|integer $exit True defaults to exit(1). * @return null - * - * @phpstan-return ($exit is true|positive-int ? never : void) */ public static function error( $message, $exit = true ) { - if ( null !== self::$logger && ! isset( self::get_runner()->assoc_args['completions'] ) ) { + if ( ! isset( self::get_runner()->assoc_args['completions'] ) ) { self::$logger->error( self::error_to_string( $message ) ); } @@ -1041,9 +757,8 @@ public static function error( $message, $exit = true ) { } if ( $return_code ) { - self::debug_backtrace_on_exit(); if ( self::$capture_exit ) { - throw new ExitException( '', $return_code ); + throw new ExitException( null, $return_code ); } exit( $return_code ); } @@ -1054,20 +769,14 @@ public static function error( $message, $exit = true ) { * * Permits script execution to be overloaded by `WP_CLI::runcommand()` * - * When `--debug` is enabled, this method will also output a backtrace - * showing where the halt was triggered from, making it easier to identify - * the cause of early termination. - * * @access public * @category Output * * @param integer $return_code - * @return never */ public static function halt( $return_code ) { - self::debug_backtrace_on_exit(); if ( self::$capture_exit ) { - throw new ExitException( '', $return_code ); + throw new ExitException( null, $return_code ); } exit( $return_code ); } @@ -1080,22 +789,11 @@ public static function halt( $return_code ) { * @access public * @category Output * - * @param array $message_lines Multi-line error message to be displayed. + * @param array $message Multi-line error message to be displayed. */ public static function error_multi_line( $message_lines ) { - if ( null === self::$logger ) { - return; - } - if ( ! isset( self::get_runner()->assoc_args['completions'] ) && is_array( $message_lines ) ) { - self::$logger->error_multi_line( - array_map( - static function ( $message ) { - return self::error_to_string( $message ); - }, - $message_lines - ) - ); + self::$logger->error_multi_line( array_map( array( __CLASS__, 'error_to_string' ), $message_lines ) ); } } @@ -1117,13 +815,13 @@ static function ( $message ) { * @param string $question Question to display before the prompt. * @param array $assoc_args Skips prompt if 'yes' is provided. */ - public static function confirm( $question, $assoc_args = [] ) { - if ( ! Utils\get_flag_value( $assoc_args, 'yes' ) ) { + public static function confirm( $question, $assoc_args = array() ) { + if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'yes' ) ) { fwrite( STDOUT, $question . ' [y/n] ' ); - $answer = strtolower( trim( (string) fgets( STDIN ) ) ); + $answer = strtolower( trim( fgets( STDIN ) ) ); - if ( 'y' !== $answer ) { + if ( 'y' != $answer ) { exit; } } @@ -1139,16 +837,13 @@ public static function confirm( $question, $assoc_args = [] ) { */ public static function get_value_from_arg_or_stdin( $args, $index ) { if ( isset( $args[ $index ] ) ) { - $raw_value = (string) $args[ $index ]; + $raw_value = $args[ $index ]; } else { // We don't use file_get_contents() here because it doesn't handle // Ctrl-D properly, when typing in the value interactively. $raw_value = ''; - $line = fgets( STDIN ); - - while ( false !== $line ) { + while ( ( $line = fgets( STDIN ) ) !== false ) { $raw_value .= $line; - $line = fgets( STDIN ); } } @@ -1161,14 +856,14 @@ public static function get_value_from_arg_or_stdin( $args, $index ) { * @access public * @category Input * - * @param string $raw_value + * @param mixed $value * @param array $assoc_args */ - public static function read_value( $raw_value, $assoc_args = [] ) { - if ( Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { + public static function read_value( $raw_value, $assoc_args = array() ) { + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { $value = json_decode( $raw_value, true ); if ( null === $value ) { - self::error( sprintf( 'Invalid JSON: %s', $raw_value ) ); + WP_CLI::error( sprintf( 'Invalid JSON: %s', $raw_value ) ); } } else { $value = $raw_value; @@ -1183,20 +878,22 @@ public static function read_value( $raw_value, $assoc_args = [] ) { * @param mixed $value Value to display. * @param array $assoc_args Arguments passed to the command, determining format. */ - public static function print_value( $value, $assoc_args = [] ) { - $format = Utils\get_flag_value( $assoc_args, 'format' ); - - $_value = \WP_CLI\Formatter::format_single_value( $value, $format ); + public static function print_value( $value, $assoc_args = array() ) { + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { + $value = json_encode( $value ); + } elseif ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'yaml' ) { + $value = Spyc::YAMLDump( $value, 2, 0 ); + } elseif ( is_array( $value ) || is_object( $value ) ) { + $value = var_export( $value ); + } - echo $_value . "\n"; + echo $value . "\n"; } /** - * Convert a WP_Error or Exception into a string - * - * @param string|WP_Error|Exception|Throwable $errors - * @throws InvalidArgumentException + * Convert a wp_error into a string * + * @param mixed $errors * @return string */ public static function error_to_string( $errors ) { @@ -1204,8 +901,8 @@ public static function error_to_string( $errors ) { return $errors; } - // Only json_encode() the data when it needs it. - $render_data = function ( $data ) { + // Only json_encode() the data when it needs it + $render_data = function( $data ) { if ( is_array( $data ) || is_object( $data ) ) { return json_encode( $data ); } @@ -1213,89 +910,15 @@ public static function error_to_string( $errors ) { return '"' . $data . '"'; }; - if ( $errors instanceof WP_Error ) { + if ( is_object( $errors ) && is_a( $errors, 'WP_Error' ) ) { foreach ( $errors->get_error_messages() as $message ) { if ( $errors->get_error_data() ) { - return $message . ' ' . (string) $render_data( $errors->get_error_data() ); + return $message . ' ' . $render_data( $errors->get_error_data() ); } return $message; } } - - if ( $errors instanceof Throwable ) { - return get_class( $errors ) . ': ' . $errors->getMessage(); - } - - throw new InvalidArgumentException( - sprintf( - "Unsupported argument type passed to WP_CLI::error_to_string(): '%s'", - gettype( $errors ) - ) - ); - } - - /** - * Output debug backtrace information when --debug is enabled. - * - * This is called when WP_CLI is about to exit (via error() or halt()) - * to help identify where the exit originated from. - * - * @access private - */ - private static function debug_backtrace_on_exit() { - // Only output backtrace when debug mode is enabled. - if ( ! self::$logger || ! self::get_config( 'debug' ) ) { - return; - } - - $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); - - // Skip the first few frames (this method, error/halt method, etc.). - $skip_frames = 0; - foreach ( $backtrace as $index => $frame ) { - // Skip internal WP_CLI methods. - if ( isset( $frame['class'] ) && 'WP_CLI' === $frame['class'] && - in_array( $frame['function'], [ 'debug_backtrace_on_exit', 'error', 'halt' ], true ) ) { - $skip_frames = $index + 1; - continue; - } - break; - } - - // Get the first relevant frame (where the error/halt was called from). - if ( isset( $backtrace[ $skip_frames ] ) ) { - $frame = $backtrace[ $skip_frames ]; - $file = $frame['file'] ?? 'unknown'; - $line = $frame['line'] ?? 'unknown'; - - self::debug( "Script called exit from: {$file}:{$line}", 'bootstrap' ); - - // Output a limited backtrace (first 5 frames after skipping internal ones). - $backtrace_output = []; - $max_frames = 5; - $frame_count = 0; - $backtrace_count = count( $backtrace ); - - for ( $i = $skip_frames; $i < $backtrace_count && $frame_count < $max_frames; $i++ ) { - $frame = $backtrace[ $i ]; - $func = $frame['function']; - - if ( isset( $frame['class'] ) ) { - $func = $frame['class'] . ( $frame['type'] ?? '::' ) . $func; - } - - $file = $frame['file'] ?? 'unknown'; - $line = $frame['line'] ?? '?'; - - $backtrace_output[] = " #{$frame_count} {$func}() called at [{$file}:{$line}]"; - ++$frame_count; - } - - if ( ! empty( $backtrace_output ) ) { - self::debug( "Backtrace:\n" . implode( "\n", $backtrace_output ), 'bootstrap' ); - } - } } /** @@ -1317,27 +940,14 @@ private static function debug_backtrace_on_exit() { * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code. * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results. * @return int|ProcessRun The command exit status, or a ProcessRun object for full details. - * - * @phpstan-return ($return_detailed is true ? ProcessRun : int) */ public static function launch( $command, $exit_on_error = true, $return_detailed = false ) { Utils\check_proc_available( 'launch' ); - // Forward environment variables when available so child processes can still - // read DB_* (and other) values via getenv() / $_ENV in wp-config.php. - $env = $_ENV; - - if ( ! empty( $env ) ) { - // Explicit env array, child process inherits only these entries. - $proc = Process::create( $command, null, $env ); - } else { - // $_ENV is empty → use null to inherit full parent environment. - $proc = Process::create( $command, null, null ); - } - + $proc = Process::create( $command ); $results = $proc->run(); - if ( -1 === $results->return_code ) { + if ( -1 == $results->return_code ) { self::warning( "Spawned process returned exit code {$results->return_code}, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option." ); } @@ -1372,49 +982,36 @@ public static function launch( $command, $exit_on_error = true, $return_detailed * @param bool $return_detailed Whether to return an exit status (default) or detailed execution results. * @param array $runtime_args Override one or more global args (path,url,user,allow-root) * @return int|ProcessRun The command exit status, or a ProcessRun instance - * - * @phpstan-return ($return_detailed is false ? int : ProcessRun) */ - public static function launch_self( $command, $args = [], $assoc_args = [], $exit_on_error = true, $return_detailed = false, $runtime_args = [] ) { - $reused_runtime_args = [ + public static function launch_self( $command, $args = array(), $assoc_args = array(), $exit_on_error = true, $return_detailed = false, $runtime_args = array() ) { + $reused_runtime_args = array( 'path', 'url', 'user', 'allow-root', - ]; + ); foreach ( $reused_runtime_args as $key ) { if ( isset( $runtime_args[ $key ] ) ) { $assoc_args[ $key ] = $runtime_args[ $key ]; - continue; - } - - $value = self::get_runner()->config[ $key ]; - if ( $value ) { + } elseif ( $value = self::get_runner()->config[ $key ] ) { $assoc_args[ $key ] = $value; } } $php_bin = escapeshellarg( Utils\get_php_binary() ); - /** - * @var string[] $argv - */ - $argv = $GLOBALS['argv']; + $script_path = $GLOBALS['argv'][0]; - $script_path = $argv[0]; - - $wp_cli_config_path = (string) getenv( 'WP_CLI_CONFIG_PATH' ); - - if ( $wp_cli_config_path ) { - $config_path = $wp_cli_config_path; + if ( getenv( 'WP_CLI_CONFIG_PATH' ) ) { + $config_path = getenv( 'WP_CLI_CONFIG_PATH' ); } else { - $config_path = Path::get_home_dir() . '/.wp-cli/config.yml'; + $config_path = Utils\get_home_dir() . '/.wp-cli/config.yml'; } $config_path = escapeshellarg( $config_path ); - $args = implode( ' ', array_map( 'escapeshellarg', (array) $args ) ); - $assoc_args = Utils\assoc_args_to_str( $assoc_args ); + $args = implode( ' ', array_map( 'escapeshellarg', $args ) ); + $assoc_args = \WP_CLI\Utils\assoc_args_to_str( $assoc_args ); $full_command = "WP_CLI_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$command} {$args} {$assoc_args}"; @@ -1437,27 +1034,11 @@ public static function get_php_binary() { return Utils\get_php_binary(); } - /** - * Confirm that a global configuration parameter does exist. - * - * @access public - * @category Input - * - * @param string $key Config parameter key to check. - * - * @return bool - * - * @phpstan-param key-of $key - */ - public static function has_config( $key ) { - return array_key_exists( $key, self::get_runner()->config ); - } - /** * Get values of global configuration parameters. * * Provides access to `--path=`, `--url=`, and other values of - * the [global configuration parameters](https://make.wordpress.org/cli/handbook/references/config/). + * the [global configuration parameters](https://wp-cli.org/config/). * * ``` * WP_CLI::log( 'The --url= value is: ' . WP_CLI::get_config( 'url' ) ); @@ -1468,16 +1049,13 @@ public static function has_config( $key ) { * * @param string $key Get value for a specific global configuration parameter. * @return mixed - * - * @phpstan-param key-of $key - * @phpstan-return ($key is null ? GlobalConfig : value-of) */ public static function get_config( $key = null ) { if ( null === $key ) { return self::get_runner()->config; } - if ( ! self::has_config( $key ) ) { + if ( ! isset( self::get_runner()->config[ $key ] ) ) { self::warning( "Unknown config option '$key'." ); return null; } @@ -1495,15 +1073,13 @@ public static function get_config( $key = null ) { * * Prevent halting script execution on error. * * Capture and return STDOUT, or full details about command execution. * * Parse JSON output if the command rendered it. - * * Include additional arguments that are passed to the command. * * ``` * $options = array( - * 'return' => true, // Return 'STDOUT'; use 'all' for full object. - * 'parse' => 'json', // Parse captured STDOUT to JSON array. - * 'launch' => false, // Reuse the current process. - * 'exit_error' => true, // Halt script execution on error. - * 'command_args' => [ '--skip-themes' ], // Additional arguments to be passed to the $command. + * 'return' => true, // Return 'STDOUT'; use 'all' for full object. + * 'parse' => 'json', // Parse captured STDOUT to JSON array. + * 'launch' => false, // Reuse the current process. + * 'exit_error' => true, // Halt script execution on error. * ); * $plugins = WP_CLI::runcommand( 'plugin list --format=json', $options ); * ``` @@ -1512,71 +1088,46 @@ public static function get_config( $key = null ) { * @category Execution * * @param string $command WP-CLI command to run, including arguments. - * @param array $options { - * Configuration options for command execution. - * - * @type bool $launch Launches a new process (true) or reuses the existing process (false). Default: true. - * @type bool $exit_error Halts the script on error. Default: true. - * @type bool|string $return Returns output as an object when set to 'all' (string), return just the 'stdout', 'stderr', or 'return_code' (string) of command, or print directly to stdout/stderr (false). Default: false. - * @type bool|string $parse Parse returned output as 'json' (string); otherwise, output is unchanged (false). Default: false. - * @type array $command_args Contains additional command line arguments for the command. Each element represents a single argument. Default: empty array. - * } + * @param array $options Configuration options for command execution. * @return mixed */ - public static function runcommand( $command, $options = [] ) { - $defaults = [ - 'launch' => true, // Launch a new process, or reuse the existing. - 'exit_error' => true, // Exit on error by default. - 'return' => false, // Capture and return output, or render in realtime. - 'parse' => false, // Parse returned output as a particular format. - 'command_args' => [], // Include optional command arguments. - ]; - $options = array_merge( $defaults, $options ); - $launch = $options['launch']; - $exit_error = $options['exit_error']; - $return = $options['return']; - $parse = $options['parse']; - $command_args = $options['command_args']; - - if ( ! empty( $command_args ) ) { - $command .= ' ' . implode( ' ', $command_args ); - } - + public static function runcommand( $command, $options = array() ) { + $defaults = array( + 'launch' => true, // Launch a new process, or reuse the existing. + 'exit_error' => true, // Exit on error by default. + 'return' => false, // Capture and return output, or render in realtime. + 'parse' => false, // Parse returned output as a particular format. + ); + $options = array_merge( $defaults, $options ); + $launch = $options['launch']; + $exit_error = $options['exit_error']; + $return = $options['return']; + $parse = $options['parse']; $retval = null; if ( $launch ) { Utils\check_proc_available( 'launch option' ); - $descriptors = [ + $descriptors = array( 0 => STDIN, 1 => STDOUT, 2 => STDERR, - ]; + ); if ( $return ) { - $descriptors = [ + $descriptors = array( 0 => STDIN, - 1 => [ 'pipe', 'w' ], - 2 => [ 'pipe', 'w' ], - ]; + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); } - /** - * @var string[] $argv - */ - $argv = $GLOBALS['argv']; - - /** - * @var array $descriptors - */ - - $php_bin = escapeshellarg( Utils\get_php_binary() ); - $script_path = $argv[0]; + $php_bin = escapeshellarg( Utils\get_php_binary() ); + $script_path = $GLOBALS['argv'][0]; // Persist runtime arguments unless they've been specified otherwise. - $configurator = self::get_configurator(); - $argv = array_slice( $argv, 1 ); - - list( $ignore1, $ignore2, $runtime_config ) = $configurator->parse_args( $argv ); + $configurator = \WP_CLI::get_configurator(); + $argv = array_slice( $GLOBALS['argv'], 1 ); + list( $_, $_, $runtime_config ) = $configurator->parse_args( $argv ); foreach ( $runtime_config as $k => $v ) { if ( preg_match( "|^--{$k}=?$|", $command ) ) { unset( $runtime_config[ $k ] ); @@ -1584,31 +1135,18 @@ public static function runcommand( $command, $options = [] ) { } $runtime_config = Utils\assoc_args_to_str( $runtime_config ); - $alias = self::get_runner()->alias; - $alias_prefix = ''; - if ( $alias && '@' !== substr( ltrim( $command ), 0, 1 ) ) { - $alias_prefix = '@' . $alias . ' '; - } - - $runcommand = "{$php_bin} {$script_path} {$alias_prefix}{$runtime_config} {$command}"; - - /** - * @phpstan-var array $pipes - */ - $pipes = []; - $proc = Utils\proc_open_compat( $runcommand, $descriptors, $pipes, getcwd() ?: null ); + $runcommand = "{$php_bin} {$script_path} {$runtime_config} {$command}"; - $stdout = ''; - $stderr = ''; + $proc = Utils\proc_open_compat( $runcommand, $descriptors, $pipes, getcwd() ); if ( $return ) { - $stdout = (string) stream_get_contents( $pipes[1] ); + $stdout = stream_get_contents( $pipes[1] ); fclose( $pipes[1] ); - $stderr = (string) stream_get_contents( $pipes[2] ); + $stderr = stream_get_contents( $pipes[2] ); fclose( $pipes[2] ); } - $return_code = $proc ? proc_close( $proc ) : -1; - if ( -1 === $return_code ) { + $return_code = proc_close( $proc ); + if ( -1 == $return_code ) { self::warning( 'Spawned process returned exit code -1, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option.' ); } elseif ( $return_code && $exit_error ) { exit( $return_code ); @@ -1620,19 +1158,19 @@ public static function runcommand( $command, $options = [] ) { } elseif ( 'return_code' === $return ) { $retval = $return_code; } elseif ( 'all' === $return ) { - $retval = (object) [ + $retval = (object) array( 'stdout' => trim( $stdout ), 'stderr' => trim( $stderr ), 'return_code' => $return_code, - ]; + ); } } else { - $configurator = self::get_configurator(); - $argv = Utils\parse_str_to_argv( $command ); + $configurator = self::get_configurator(); + $argv = Utils\parse_str_to_argv( $command ); list( $args, $assoc_args, $runtime_config ) = $configurator->parse_args( $argv ); if ( $return ) { $existing_logger = self::$logger; - self::$logger = new Execution(); + self::$logger = new WP_CLI\Loggers\Execution; self::$logger->ob_start(); } if ( ! $exit_error ) { @@ -1640,11 +1178,9 @@ public static function runcommand( $command, $options = [] ) { } try { self::get_runner()->run_command( - $args, - $assoc_args, - [ + $args, $assoc_args, array( 'back_compat_conversions' => true, - ] + ) ); $return_code = 0; } catch ( ExitException $e ) { @@ -1654,8 +1190,8 @@ public static function runcommand( $command, $options = [] ) { $execution_logger = self::$logger; $execution_logger->ob_end(); self::$logger = $existing_logger; - $stdout = $execution_logger->stdout; - $stderr = $execution_logger->stderr; + $stdout = $execution_logger->stdout; + $stderr = $execution_logger->stderr; if ( true === $return || 'stdout' === $return ) { $retval = trim( $stdout ); } elseif ( 'stderr' === $return ) { @@ -1663,18 +1199,19 @@ public static function runcommand( $command, $options = [] ) { } elseif ( 'return_code' === $return ) { $retval = $return_code; } elseif ( 'all' === $return ) { - $retval = (object) [ + $retval = (object) array( 'stdout' => trim( $stdout ), 'stderr' => trim( $stderr ), 'return_code' => $return_code, - ]; + ); } } if ( ! $exit_error ) { self::$capture_exit = false; } } - if ( ( true === $return || 'stdout' === $return ) && 'json' === $parse ) { + if ( ( true === $return || 'stdout' === $return ) + && 'json' === $parse ) { $retval = json_decode( $retval, true ); } return $retval; @@ -1702,33 +1239,31 @@ public static function runcommand( $command, $options = [] ) { * @param array $args Positional arguments including command name. * @param array $assoc_args */ - public static function run_command( $args, $assoc_args = [] ) { + public static function run_command( $args, $assoc_args = array() ) { self::get_runner()->run_command( $args, $assoc_args ); } - // DEPRECATED STUFF. + // DEPRECATED STUFF public static function add_man_dir() { trigger_error( 'WP_CLI::add_man_dir() is deprecated. Add docs inline.', E_USER_WARNING ); } - // back-compat. + // back-compat public static function out( $str ) { fwrite( STDOUT, $str ); } - // back-compat. - // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Deprecated method. + // back-compat + // @codingStandardsIgnoreLine public static function addCommand( $name, $class ) { trigger_error( sprintf( 'wp %s: %s is deprecated. use WP_CLI::add_command() instead.', - $name, - __FUNCTION__ - ), - E_USER_WARNING + $name, __FUNCTION__ + ), E_USER_WARNING ); self::add_command( $name, $class ); } diff --git a/php/commands/cli.php b/php/commands/cli.php index eaf721de1..e1abb4937 100644 --- a/php/commands/cli.php +++ b/php/commands/cli.php @@ -1,19 +1,3 @@ ...] + * : Get help on a specific command. + * + * ## EXAMPLES + * + * # get help for `core` command + * wp help core + * + * # get help for `core download` subcommand + * wp help core download + */ + public function __invoke( $args, $assoc_args ) { + $r = WP_CLI::get_runner()->find_command_to_run( $args ); + + if ( is_array( $r ) ) { + list( $command ) = $r; + + self::show_help( $command ); + exit; + } + } + + private static function show_help( $command ) { + $out = self::get_initial_markdown( $command ); + + // Remove subcommands if in columns - will wordwrap separately. + $subcommands = ''; + $column_subpattern = '[ \t]+[^\t]+\t+'; + if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $matches, PREG_OFFSET_CAPTURE ) ) { + $subcommands = $matches[1][0]; + $subcommands_header = "## SUBCOMMANDS\n"; + $out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) ); + } + + $out .= self::parse_reference_links( $command->get_longdesc() ); + + // definition lists + $out = preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', array( __CLASS__, 'rewrap_param_desc' ), $out ); + + // Ensure all non-section headers are indented. + $out = preg_replace( '#^([^\s^\#])#m', "\t$1", $out ); + + $tab = str_repeat( ' ', 2 ); + + // Need to de-tab for wordwrapping to work properly. + $out = str_replace( "\t", $tab, $out ); + + $wordwrap_width = \cli\Shell::columns(); + + // Wordwrap with indent. + $out = preg_replace_callback( + '/^( *)([^\n]+)\n/m', + function ( $matches ) use ( $wordwrap_width ) { + return $matches[1] . str_replace( "\n", "\n{$matches[1]}", wordwrap( $matches[2], $wordwrap_width - strlen( $matches[1] ) ) ) . "\n"; + }, + $out + ); + + if ( $subcommands ) { + // Wordwrap with column indent. + $subcommands = preg_replace_callback( + '/^(' . $column_subpattern . ')([^\n]+)\n/m', + function ( $matches ) use ( $wordwrap_width, $tab ) { + // Need to de-tab for wordwrapping to work properly. + $matches[1] = str_replace( "\t", $tab, $matches[1] ); + $matches[2] = str_replace( "\t", $tab, $matches[2] ); + $padding_len = strlen( $matches[1] ); + $padding = str_repeat( ' ', $padding_len ); + return $matches[1] . str_replace( "\n", "\n$padding", wordwrap( $matches[2], $wordwrap_width - $padding_len ) ) . "\n"; + }, + $subcommands + ); + + // Put subcommands back. + $out = str_replace( $subcommands_header, $subcommands, $out ); + } + + // section headers + $out = preg_replace( '/^## ([A-Z ]+)/m', WP_CLI::colorize( '%9\1%n' ), $out ); + + self::pass_through_pager( $out ); + } + + private static function rewrap_param_desc( $matches ) { + $param = $matches[1]; + $desc = self::indent( "\t\t", $matches[2] ); + return "\t$param\n$desc\n\n"; + } + + private static function indent( $whitespace, $text ) { + $lines = explode( "\n", $text ); + foreach ( $lines as &$line ) { + $line = $whitespace . $line; + } + return implode( $lines, "\n" ); + } + + private static function pass_through_pager( $out ) { + + if ( ! Utils\check_proc_available( null /*context*/, true /*return*/ ) ) { + WP_CLI::debug( 'Warning: check_proc_available() failed in pass_through_pager().', 'help' ); + return $out; + } + + if ( false === ( $pager = getenv( 'PAGER' ) ) ) { + $pager = Utils\is_windows() ? 'more' : 'less -r'; + } + + // For Windows 7 need to set code page to something other than Unicode (65001) to get around "Not enough memory." error with `more.com` on PHP 7.1+. + if ( 'more' === $pager && defined( 'PHP_WINDOWS_VERSION_MAJOR' ) && PHP_WINDOWS_VERSION_MAJOR < 10 && function_exists( 'sapi_windows_cp_set' ) ) { + // Note will also apply to Windows 8 (see http://msdn.microsoft.com/en-us/library/windows/desktop/ms724832.aspx) but probably harmless anyway. + $cp = getenv( 'WP_CLI_WINDOWS_CODE_PAGE' ) ?: 1252; // Code page 1252 is the most used so probably the most compat. + sapi_windows_cp_set( $cp ); // `sapi_windows_cp_set()` introduced PHP 7.1. + } + + // convert string to file handle + $fd = fopen( 'php://temp', 'r+b' ); + fwrite( $fd, $out ); + rewind( $fd ); + + $descriptorspec = array( + 0 => $fd, + 1 => STDOUT, + 2 => STDERR, + ); + + return proc_close( Utils\proc_open_compat( $pager, $descriptorspec, $pipes ) ); + } + + private static function get_initial_markdown( $command ) { + $name = implode( ' ', Dispatcher\get_path( $command ) ); + + $binding = array( + 'name' => $name, + 'shortdesc' => $command->get_shortdesc(), + ); + + $binding['synopsis'] = "$name " . $command->get_synopsis(); + + $alias = $command->get_alias(); + if ( $alias ) { + $binding['alias'] = $alias; + } + + if ( $command->can_have_subcommands() ) { + $binding['has-subcommands']['subcommands'] = self::render_subcommands( $command ); + } + + return Utils\mustache_render( 'man.mustache', $binding ); + } + + private static function render_subcommands( $command ) { + $subcommands = array(); + foreach ( $command->get_subcommands() as $subcommand ) { + + if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) { + continue; + } + + $subcommands[ $subcommand->get_name() ] = $subcommand->get_shortdesc(); + } + + $max_len = self::get_max_len( array_keys( $subcommands ) ); + + $lines = array(); + foreach ( $subcommands as $name => $desc ) { + $lines[] = str_pad( $name, $max_len ) . "\t\t\t" . $desc; + } + + return $lines; + } + + private static function get_max_len( $strings ) { + $max_len = 0; + foreach ( $strings as $str ) { + $len = strlen( $str ); + if ( $len > $max_len ) { + $max_len = $len; + } + } + + return $max_len; + } + + /** + * Parse reference links from longdescription. + * + * @param string $longdesc The longdescription from the `$command->get_longdesc()`. + * @return string The longdescription which has links as footnote. + */ + private static function parse_reference_links( $longdesc ) { + $description = ''; + foreach ( explode( "\n", $longdesc ) as $line ) { + if ( 0 === strpos( $line, '#' ) ) { + break; + } + $description .= $line . "\n"; + } + + // Fires if it has description text at the head of `$longdesc`. + if ( $description ) { + $links = array(); // An array of URLs from the description. + $pattern = '/\[.+?\]\((https?:\/\/.+?)\)/'; + $newdesc = preg_replace_callback( $pattern, function( $matches ) use ( &$links ) { + static $count = 0; + $count++; + $links[] = $matches[1]; + return str_replace( '(' . $matches[1] . ')', '[' . $count . ']', $matches[0] ); + }, $description ); + + $footnote = ''; + for ( $i = 0; $i < count( $links ); $i++ ) { + $n = $i + 1; + $footnote .= '[' . $n . '] ' . $links[ $i ] . "\n"; + } + + if ( $footnote ) { + $newdesc = trim( $newdesc ) . "\n\n---\n" . $footnote; + $longdesc = str_replace( trim( $description ), trim( $newdesc ), $longdesc ); + } + } + + return $longdesc; + } } WP_CLI::add_command( 'help', 'Help_Command' ); + diff --git a/php/commands/src/CLI_Alias_Command.php b/php/commands/src/CLI_Alias_Command.php deleted file mode 100644 index 8defdeaaf..000000000 --- a/php/commands/src/CLI_Alias_Command.php +++ /dev/null @@ -1,770 +0,0 @@ -] - * : Render output in a particular format. - * --- - * default: yaml - * options: - * - yaml - * - json - * - var_export - * --- - * - * [--raw] - * : Display aliases without interpolating environment variables. - * - * ## EXAMPLES - * - * # List all available aliases. - * $ wp cli alias list - * --- - * @all: Run command against every registered alias. - * @prod: - * ssh: runcommand@runcommand.io~/webapps/production - * @dev: - * ssh: vagrant@192.168.50.10/srv/www/runcommand.dev - * @both: - * - @prod - * - @dev - * - * # List aliases without environment variable interpolation. - * $ wp cli alias list --raw - * - * @subcommand list - * - * @param array $args Positional arguments. Unused. - * @param array{format: string, raw?: bool} $assoc_args Associative arguments. - */ - public function list_( $args, $assoc_args ) { - $raw = Utils\get_flag_value( $assoc_args, 'raw', false ); - $aliases = $raw ? WP_CLI::get_runner()->raw_aliases : WP_CLI::get_runner()->aliases; - - // Add @ prefix to aliases for display (backward compatibility) - $display_aliases = []; - foreach ( $aliases as $alias => $value ) { - $display_alias = '@' . $alias; - if ( is_array( $value ) ) { - // Check if it's a group (numeric indexed array) - if ( isset( $value[0] ) && is_string( $value[0] ) ) { - /** @var string[] $value */ - // It's a group, add @ prefix to each member - $display_aliases[ $display_alias ] = array_map( - function ( $member ) { - return '@' . $member; - }, - $value - ); - } else { - // It's a regular alias config - $display_aliases[ $display_alias ] = $value; - } - } else { - // It's a string (like the 'all' description) - $display_aliases[ $display_alias ] = $value; - } - } - - WP_CLI::print_value( $display_aliases, $assoc_args ); - } - - /** - * Gets the value for an alias. - * - * ## OPTIONS - * - * - * : Key for the alias. - * - * [--field=] - * : Get the value of a specific field. - * - * [--raw] - * : Display alias without interpolating environment variables. - * - * ## EXAMPLES - * - * # Get alias. - * $ wp cli alias get @prod - * ssh: dev@somedeve.env:12345/home/dev/ - * - * # Get a specific field of an alias. - * $ wp cli alias get @prod --field=ssh - * dev@somedeve.env:12345/home/dev/ - * - * # Get alias without environment variable interpolation. - * $ wp cli alias get @prod --raw - * ssh: ${env.PROD_USER}@${env.PROD_HOST}:${env.PROD_PATH} - * - * @param array{string} $args Positional arguments. - * @param array{field?: string, raw?: bool} $assoc_args Associative arguments. - */ - public function get( $args, $assoc_args = [] ) { - list( $alias ) = $args; - - // Normalize alias (remove @ prefix if present) - $alias = ltrim( $alias, '@' ); - - $raw = Utils\get_flag_value( $assoc_args, 'raw', false ); - $aliases = $raw ? WP_CLI::get_runner()->raw_aliases : WP_CLI::get_runner()->aliases; - - if ( empty( $aliases[ $alias ] ) ) { - WP_CLI::error( "No alias found with key '@{$alias}'." ); - } - - $field = Utils\get_flag_value( $assoc_args, 'field', null ); - - if ( null !== $field ) { - if ( ! array_key_exists( $field, $aliases[ $alias ] ) ) { - WP_CLI::error( "The '{$field}' property does not exist for '@{$alias}'." ); - } - WP_CLI::log( $aliases[ $alias ][ $field ] ); - return; - } - - foreach ( $aliases[ $alias ] as $key => $value ) { - WP_CLI::log( "{$key}: {$value}" ); - } - } - - /** - * Creates an alias. - * - * ## OPTIONS - * - * - * : Key for the alias. - * - * [--set-user=] - * : Set user for alias. - * - * [--set-url=] - * : Set url for alias. - * - * [--set-path=] - * : Set path for alias. - * - * [--set-ssh=] - * : Set ssh for alias. - * - * [--set-http=] - * : Set http for alias. - * - * [--grouping=] - * : For grouping multiple aliases. - * - * [--config=] - * : Config file to be considered for operations. - * --- - * default: global - * options: - * - global - * - project - * --- - * - * ## EXAMPLES - * - * # Add alias to global config. - * $ wp cli alias add @prod --set-ssh=login@host --set-path=/path/to/wordpress/install/ --set-user=wpcli - * Success: Added '@prod' alias. - * - * # Add alias to project config. - * $ wp cli alias add @prod --set-ssh=login@host --set-path=/path/to/wordpress/install/ --set-user=wpcli --config=project - * Success: Added '@prod' alias. - * - * # Add group of aliases. - * $ wp cli alias add @multiservers --grouping=servera,serverb - * Success: Added '@multiservers' alias. - * - * @param array{string} $args Positional arguments. - * @param array{'set-user'?: string, 'set-url'?: string, 'set-path'?: string, 'set-ssh'?: string, 'set-http'?: string, grouping?: string, config?: string} $assoc_args Associative arguments. - */ - public function add( $args, $assoc_args ) { - - $config = ( ! empty( $assoc_args['config'] ) ? $assoc_args['config'] : 'global' ); - - list( $config_path, $aliases ) = $this->get_aliases_data( $config, '', true ); - - $this->validate_config_file( $config_path ); - - $alias = $args[0]; - - $grouping = Utils\get_flag_value( $assoc_args, 'grouping' ); - - $this->validate_input( $assoc_args, $grouping ); - - $existing_key = $this->find_alias_key( $aliases, $alias ); - if ( null !== $existing_key ) { - WP_CLI::error( "Key '@" . $this->normalize_alias( $alias ) . "' exists already." ); - } - - // When adding new aliases, normalize the key (no @ prefix) - $normalized_alias = $this->normalize_alias( $alias ); - - if ( null === $grouping ) { - $aliases = $this->build_aliases( $aliases, $normalized_alias, $assoc_args, false ); - } else { - $aliases = $this->build_aliases( $aliases, $normalized_alias, $assoc_args, true, $grouping ); - } - - $this->process_aliases( $aliases, $alias, $config_path, 'Added' ); - } - - /** - * Deletes an alias. - * - * ## OPTIONS - * - * - * : Key for the alias. - * - * [--config=] - * : Config file to be considered for operations. - * --- - * options: - * - global - * - project - * --- - * - * ## EXAMPLES - * - * # Delete alias. - * $ wp cli alias delete @prod - * Success: Deleted '@prod' alias. - * - * # Delete project alias. - * $ wp cli alias delete @prod --config=project - * Success: Deleted '@prod' alias. - * - * @param array{string} $args Positional arguments. - * @param array{config?: string} $assoc_args Associative arguments - */ - public function delete( $args, $assoc_args ) { - - list( $alias ) = $args; - - $config = ( ! empty( $assoc_args['config'] ) ? $assoc_args['config'] : '' ); - - list( $config_path, $aliases ) = $this->get_aliases_data( $config, $alias ); - - $this->validate_config_file( $config_path ); - - $alias_key = $this->find_alias_key( $aliases, $alias ); - if ( null === $alias_key ) { - WP_CLI::error( "No alias found with key '@" . $this->normalize_alias( $alias ) . "'." ); - } - - unset( $aliases[ $alias_key ] ); - $this->process_aliases( $aliases, $alias, $config_path, 'Deleted' ); - } - - /** - * Updates an alias. - * - * ## OPTIONS - * - * - * : Key for the alias. - * - * [--set-user=] - * : Set user for alias. - * - * [--set-url=] - * : Set url for alias. - * - * [--set-path=] - * : Set path for alias. - * - * [--set-ssh=] - * : Set ssh for alias. - * - * [--set-http=] - * : Set http for alias. - * - * [--grouping=] - * : For grouping multiple aliases. - * - * [--config=] - * : Config file to be considered for operations. - * --- - * options: - * - global - * - project - * --- - * - * ## EXAMPLES - * - * # Update alias. - * $ wp cli alias update @prod --set-user=newuser --set-path=/new/path/to/wordpress/install/ - * Success: Updated 'prod' alias. - * - * # Update project alias. - * $ wp cli alias update @prod --set-user=newuser --set-path=/new/path/to/wordpress/install/ --config=project - * Success: Updated 'prod' alias. - * - * @param array{string} $args Positional arguments. - * @param array{'set-user'?: string, 'set-url'?: string, 'set-path'?: string, 'set-ssh'?: string, 'set-http'?: string, grouping?: string, config?: string} $assoc_args Associative arguments. - */ - public function update( $args, $assoc_args ) { - - $config = ( ! empty( $assoc_args['config'] ) ? $assoc_args['config'] : '' ); - $alias = $args[0]; - - $grouping = Utils\get_flag_value( $assoc_args, 'grouping' ); - - list( $config_path, $aliases ) = $this->get_aliases_data( $config, $alias, true ); - - $this->validate_config_file( $config_path ); - - $this->validate_input( $assoc_args, $grouping ); - - $alias_key = $this->find_alias_key( $aliases, $alias ); - if ( null === $alias_key ) { - WP_CLI::error( "No alias found with key '@" . $this->normalize_alias( $alias ) . "'." ); - } - - // For updates, we need to work with the actual YAML key - // Pass the alias_key to build_aliases which will be normalized internally - // But we need to remove the old key and add with the new one if structure changed - if ( null === $grouping ) { - $aliases = $this->build_aliases( $aliases, $alias_key, $assoc_args, false, '', true ); - } else { - $aliases = $this->build_aliases( $aliases, $alias_key, $assoc_args, true, $grouping, true ); - } - - $this->process_aliases( $aliases, $alias, $config_path, 'Updated' ); - } - - /** - * Check whether an alias is a group. - * - * ## OPTIONS - * - * - * : Key for the alias. - * - * ## EXAMPLES - * - * # Checks whether the alias is a group; exit status 0 if it is, otherwise 1. - * $ wp cli alias is-group @prod - * $ echo $? - * 1 - * - * @subcommand is-group - */ - public function is_group( $args, $assoc_args = array() ) { - $alias = ltrim( $args[0], '@' ); - - $aliases = WP_CLI::get_runner()->aliases; - - if ( empty( $aliases[ $alias ] ) ) { - WP_CLI::error( "No alias found with key '@{$alias}'." ); - } - - // how do we know the alias is a group? - // + array keys are numeric - // + array values are strings (group members) - - $first_item = $aliases[ $alias ]; - $first_item_key = key( $first_item ); - - if ( is_numeric( $first_item_key ) ) { - WP_CLI::halt( 0 ); - } - WP_CLI::halt( 1 ); - } - - /** - * Get config path and aliases data based on config type. - * - * @param string $config Type of config to get data from. - * @param string $alias Alias to be used for Add/Update/Delete. - * @param bool $create_config_file Optional. If a config file doesn't exist, - * should it be created? Defaults to false. - * - * @return array Config Path and Aliases in it. - * @throws ExitException - */ - private function get_aliases_data( $config, $alias, $create_config_file = false ) { - - $global_config_path = WP_CLI::get_runner()->get_global_config_path( $create_config_file ); - $global_aliases = Spyc::YAMLLoad( $global_config_path ); - - $project_config_path = WP_CLI::get_runner()->get_project_config_path(); - $project_aliases = Spyc::YAMLLoad( $project_config_path ); - - if ( 'global' === $config ) { - $config_path = $global_config_path; - $aliases = $global_aliases; - } elseif ( 'project' === $config ) { - $config_path = $project_config_path; - $aliases = $project_aliases; - } else { - - $is_global_alias = null !== $this->find_alias_key( $global_aliases, $alias ); - $is_project_alias = null !== $this->find_alias_key( $project_aliases, $alias ); - - if ( $is_global_alias && $is_project_alias ) { - WP_CLI::error( "Key '@" . $this->normalize_alias( $alias ) . "' found in more than one path. Please pass --config param." ); - } elseif ( $is_global_alias ) { - $config_path = $global_config_path; - $aliases = $global_aliases; - } else { - $config_path = $project_config_path; - $aliases = $project_aliases; - } - } - - return [ $config_path, $aliases ]; - } - - /** - * Check if the config file exists and is writable. - * - * @param string $config_path Path to config file. - */ - private function validate_config_file( $config_path ): void { - if ( ! file_exists( $config_path ) || ! is_writable( $config_path ) ) { - WP_CLI::error( "Config file does not exist: {$config_path}" ); - } - } - - /** - * Return aliases array. - * - * @param array $aliases Current aliases data. - * @param string $alias Name of alias. - * @param array $assoc_args Associative arguments. - * @param bool $is_grouping Check if its a grouping operation. - * @param string $grouping Grouping value. - * @param bool $is_update Is this an update operation? - * - * @return array> - */ - private function build_aliases( $aliases, $alias, $assoc_args, $is_grouping, $grouping = '', $is_update = false ) { - // For updates, we might receive @foo or foo depending on YAML format - // Normalize it for consistency - $normalized_alias = $this->normalize_alias( $alias ); - - if ( $is_grouping ) { - $valid_assoc_args = [ 'config', 'grouping' ]; - $invalid_args = array_diff( array_keys( $assoc_args ), $valid_assoc_args ); - - // Check for invalid args. - if ( ! empty( $invalid_args ) ) { - $args_info = implode( ',', $invalid_args ); - WP_CLI::error( "--grouping argument works alone. Found invalid arg(s) '$args_info'." ); - } - } - - // Validate BEFORE modifying the aliases array - if ( $is_update ) { - $this->validate_alias_type( $aliases, $alias, $assoc_args, $grouping ); - } - - // If updating, we need to preserve existing data and only update specified fields - $existing_data = []; - if ( $is_update ) { - // Find the existing alias data to preserve it - $alias_key = $this->find_alias_key( $aliases, $alias ); - if ( null !== $alias_key ) { - // Get existing data based on format - if ( isset( $aliases['aliases'][ $normalized_alias ] ) ) { - $existing_data = $aliases['aliases'][ $normalized_alias ]; - } elseif ( isset( $aliases[ $alias_key ] ) ) { - $existing_data = $aliases[ $alias_key ]; - } - } - - // Remove the old key structure - if ( isset( $aliases[ $alias ] ) ) { - unset( $aliases[ $alias ] ); - } - // Also check if it's in the @format - $at_key = '@' . $normalized_alias; - if ( isset( $aliases[ $at_key ] ) ) { - unset( $aliases[ $at_key ] ); - } - // Check if it's under aliases: - if ( isset( $aliases['aliases'][ $normalized_alias ] ) ) { - unset( $aliases['aliases'][ $normalized_alias ] ); - } - } - - if ( ! $is_grouping ) { - // Start with existing data for updates, or empty array for new aliases - if ( ! isset( $aliases[ $normalized_alias ] ) ) { - $aliases[ $normalized_alias ] = $existing_data; - } - - foreach ( $assoc_args as $key => $value ) { - if ( strpos( $key, 'set-' ) !== false ) { - $alias_key_info = explode( '-', $key ); - $alias_key = empty( $alias_key_info[1] ) ? '' : $alias_key_info[1]; - if ( ! empty( $alias_key ) && ! empty( $value ) ) { - $aliases[ $normalized_alias ][ $alias_key ] = $value; - } - } - } - } elseif ( ! empty( $grouping ) ) { - $group_alias_list = explode( ',', $grouping ); - $group_alias = array_map( - function ( $current_alias ) { - // Remove @ prefix if present - return ltrim( $current_alias, '@' ); - }, - $group_alias_list - ); - $aliases[ $normalized_alias ] = $group_alias; - } - - return $aliases; - } - - /** - * Validate input of passed arguments. - * - * @param array $assoc_args Arguments array. - * @param string|null $grouping Grouping argument value. - * - * @throws ExitException - */ - private function validate_input( $assoc_args, $grouping ) { - // Check if valid arguments were passed. - $arg_match = (array) preg_grep( '/^set-(\w+)/i', array_keys( $assoc_args ) ); - - // Verify passed-arguments. - if ( empty( $grouping ) && empty( $arg_match ) ) { - WP_CLI::error( 'No valid arguments passed.' ); - } - - // Check whether passed arguments contain value or not. - $assoc_arg_values = array_filter( array_intersect_key( $assoc_args, array_flip( $arg_match ) ) ); - - if ( empty( $grouping ) && empty( $assoc_arg_values ) ) { - WP_CLI::error( 'No value passed to arguments.' ); - } - } - - /** - * Validate alias type before update. - * - * @param array $aliases Existing aliases data. - * @param string $alias Alias Name (can be normalized or with @). - * @param array $assoc_args Arguments array. - * @param string $grouping Grouping argument value. - * - * @throws ExitException - */ - private function validate_alias_type( $aliases, $alias, $assoc_args, $grouping ) { - - // Find the actual key in YAML - $alias_key = $this->find_alias_key( $aliases, $alias ); - if ( null === $alias_key ) { - $alias_data = null; - } elseif ( isset( $aliases['aliases'] ) && isset( $aliases['aliases'][ $alias_key ] ) ) { - $alias_data = $aliases['aliases'][ $alias_key ]; - } else { - $alias_data = $aliases[ $alias_key ]; - } - - // Handle null or non-array data - if ( ! is_array( $alias_data ) ) { - $alias_data = []; - } - - // Check if this is a group alias by looking for numeric keys with string values - // Group aliases are stored as arrays like ['foo', 'bar'] without @ prefix - $is_group_alias = false; - if ( ! empty( $alias_data ) ) { - $numeric_keys = array_filter( array_keys( $alias_data ), 'is_numeric' ); - if ( count( $numeric_keys ) === count( $alias_data ) ) { - // All keys are numeric, so this is a group alias - $is_group_alias = true; - } - } - - $arg_match = preg_grep( '/^set-(\w+)/i', array_keys( $assoc_args ) ); - - if ( $is_group_alias && ! empty( $arg_match ) ) { - WP_CLI::error( 'Trying to update group alias with invalid arguments.' ); - } elseif ( ! $is_group_alias && ! empty( $grouping ) ) { - WP_CLI::error( 'Trying to update simple alias with invalid --grouping argument.' ); - } - } - - /** - * Save aliases data to config file. - * - * @param array $aliases Current aliases data. - * @param string $alias Name of alias. - * @param string $config_path Path to config file. - * @param string $operation Current operation string fro message. - */ - private function process_aliases( $aliases, $alias, $config_path, $operation = '' ) { - $alias = $this->normalize_alias( $alias ); - - // Convert aliases to use the new 'aliases:' format for better cross-platform compatibility - // Move any @-prefixed keys into the aliases: section - $yaml_data = []; - $aliases_section = []; - - foreach ( $aliases as $key => $value ) { - // Skip special config keys that aren't aliases - if ( in_array( $key, [ 'require', 'exec', 'disabled_commands', 'apache_modules', 'path', '_', 'url', 'user', 'ssh', 'http', 'color', 'debug', 'prompt', 'quiet', 'allow-root', 'skip-plugins', 'skip-themes', 'skip-packages', 'context', 'alias' ], true ) ) { - $yaml_data[ $key ] = $value; - } elseif ( 0 === strpos( $key, '@' ) ) { - // Convert @foo to aliases: { foo: } format - $normalized_key = substr( $key, 1 ); - $aliases_section[ $normalized_key ] = $value; - } elseif ( 'aliases' === $key ) { - // Already in aliases format, merge it - if ( is_array( $value ) ) { - $aliases_section = array_merge( $aliases_section, $value ); - } - } elseif ( is_array( $value ) ) { - // This is an alias (either config or group), add to aliases section - $aliases_section[ $key ] = $value; - } else { - // Non-alias config value - $yaml_data[ $key ] = $value; - } - } - - // Add the aliases section if we have any - if ( ! empty( $aliases_section ) ) { - $yaml_data['aliases'] = $aliases_section; - } - - // Convert data to YAML string. - $yaml_output = Spyc::YAMLDump( $yaml_data ); - - // Add data in config file. - if ( file_put_contents( $config_path, $yaml_output ) ) { - WP_CLI::success( "$operation '{$alias}' alias." ); - } - } - - /** - * Normalize the alias to an expected format. - * - * - Remove @ if present. - * - * @param string $alias Name of alias. - */ - private function normalize_alias( $alias ) { - // Remove the @ prefix if present for storage - // See: https://github.com/wp-cli/wp-cli/issues/5391 - return ltrim( $alias, '@' ); - } - - /** - * Find the actual key used for an alias in YAML data. - * - * Handles both @foo format and aliases: { foo: } format. - * - * @param array $yaml_data The raw YAML data. - * @param string $alias The alias name (with or without @). - * @return string|null The actual key in YAML, or null if not found. - */ - private function find_alias_key( $yaml_data, $alias ) { - $normalized = $this->normalize_alias( $alias ); - - // Check for @foo format - $at_key = '@' . $normalized; - if ( array_key_exists( $at_key, $yaml_data ) ) { - return $at_key; - } - - // Check for aliases: { foo: } format - if ( isset( $yaml_data['aliases'] ) && is_array( $yaml_data['aliases'] ) ) { - if ( array_key_exists( $normalized, $yaml_data['aliases'] ) ) { - return $normalized; - } - } - - return null; - } -} diff --git a/php/commands/src/CLI_Cache_Command.php b/php/commands/src/CLI_Cache_Command.php deleted file mode 100644 index 49744d134..000000000 --- a/php/commands/src/CLI_Cache_Command.php +++ /dev/null @@ -1,65 +0,0 @@ -is_enabled() ) { - WP_CLI::error( 'Cache directory does not exist.' ); - } - - $cache->clear(); - - WP_CLI::success( 'Cache cleared.' ); - } - - /** - * Prunes the internal cache. - * - * Removes all cached files except for the newest version of each one. - * - * ## EXAMPLES - * - * $ wp cli cache prune - * Success: Cache pruned. - * - * @subcommand prune - */ - public function cache_prune() { - $cache = WP_CLI::get_cache(); - - if ( ! $cache->is_enabled() ) { - WP_CLI::error( 'Cache directory does not exist.' ); - } - - $cache->prune(); - - WP_CLI::success( 'Cache pruned.' ); - } -} diff --git a/php/commands/src/CLI_Command.php b/php/commands/src/CLI_Command.php index 70b6b2b49..fa2474fd7 100644 --- a/php/commands/src/CLI_Command.php +++ b/php/commands/src/CLI_Command.php @@ -1,14 +1,10 @@ } - * - * @phpstan-type UpdateOffer array{version: string, update_type: string, package_url: string, status: string, requires_php: string} */ class CLI_Command extends WP_CLI_Command { - /** - * Memory limit threshold for warnings (512M in bytes). - */ - private const MEMORY_LIMIT_WARNING_THRESHOLD = 536870912; - private function command_to_array( $command ) { - $dump = [ - 'name' => $command->get_name(), + $dump = array( + 'name' => $command->get_name(), 'description' => $command->get_shortdesc(), - 'longdesc' => $command->get_longdesc(), - 'hook' => $command->get_hook(), - ]; - - $alias = $command->get_alias(); - if ( $alias ) { - $dump['alias'] = $alias; - } + 'longdesc' => $command->get_longdesc(), + ); foreach ( $command->get_subcommands() as $subcommand ) { $dump['subcommands'][] = $this->command_to_array( $subcommand ); @@ -69,7 +46,7 @@ private function command_to_array( $command ) { } /** - * Prints WP-CLI version. + * Print WP-CLI version. * * ## EXAMPLES * @@ -82,7 +59,7 @@ public function version() { } /** - * Prints various details about the WP-CLI environment. + * Print various details about the WP-CLI environment. * * Helpful for diagnostic purposes, this command shares: * @@ -90,14 +67,13 @@ public function version() { * * Shell information. * * PHP binary used. * * PHP binary version. - * * PHP memory limit. * * php.ini configuration file used (which is typically different than web). * * WP-CLI root dir: where WP-CLI is installed (if non-Phar install). * * WP-CLI global config: where the global config YAML file is located. * * WP-CLI project config: where the project config YAML file is located. * * WP-CLI version: currently installed version. * - * See [config docs](https://make.wordpress.org/cli/handbook/references/config/) for more details on global + * See [config docs](https://wp-cli.org/config/) for more details on global * and project config YAML files. * * ## OPTIONS @@ -119,173 +95,62 @@ public function version() { * Shell: /usr/bin/zsh * PHP binary: /usr/bin/php * PHP version: 7.1.12-1+ubuntu16.04.1+deb.sury.org+1 - * PHP memory limit: 512M * php.ini used: /etc/php/7.1/cli/php.ini * WP-CLI root dir: phar://wp-cli.phar * WP-CLI packages dir: /home/person/.wp-cli/packages/ * WP-CLI global config: * WP-CLI project config: * WP-CLI version: 1.5.0 - * - * @param string[] $args Positional arguments. Unused. - * @param array{format: string} $assoc_args Associative arguments. */ - public function info( $args, $assoc_args ) { - $system_os = sprintf( - '%s %s %s %s', - php_uname( 's' ), - php_uname( 'r' ), - php_uname( 'v' ), - php_uname( 'm' ) - ); + public function info( $_, $assoc_args ) { + $php_bin = Utils\get_php_binary(); - $shell = getenv( 'SHELL' ); + $system_os = sprintf( '%s %s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'v' ), php_uname( 'm' ) ); + $shell = getenv( 'SHELL' ); if ( ! $shell && Utils\is_windows() ) { $shell = getenv( 'ComSpec' ); } - - $php_bin = Utils\get_php_binary(); - $runner = WP_CLI::get_runner(); $packages_dir = $runner->get_packages_dir_path(); if ( ! is_dir( $packages_dir ) ) { $packages_dir = null; } - - $memory_limit = ini_get( 'memory_limit' ); - - if ( Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { - $info = [ - 'system_os' => $system_os, - 'shell' => $shell, + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) { + $info = array( 'php_binary_path' => $php_bin, - 'php_version' => PHP_VERSION, - 'php_memory_limit' => $memory_limit, - 'php_ini_used' => get_cfg_var( 'cfg_file_path' ), - 'mysql_binary_path' => Utils\get_mysql_binary_path(), - 'mysql_version' => Utils\get_mysql_version(), - 'sql_modes' => Utils\get_sql_modes(), + 'global_config_path' => $runner->global_config_path, + 'project_config_path' => $runner->project_config_path, 'wp_cli_dir_path' => WP_CLI_ROOT, - 'wp_cli_vendor_path' => WP_CLI_VENDOR_DIR, - 'wp_cli_phar_path' => defined( 'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH : '', - 'wp_cli_packages_dir_path' => $packages_dir ? Path::normalize( $packages_dir ) : null, - 'wp_cli_cache_dir_path' => Path::normalize( Utils\get_cache_dir() ), - 'global_config_path' => Path::normalize( (string) $runner->global_config_path ), - 'project_config_path' => Path::normalize( (string) $runner->project_config_path ), + 'wp_cli_packages_dir_path' => $packages_dir, 'wp_cli_version' => WP_CLI_VERSION, - ]; + 'system_os' => $system_os, + 'shell' => $shell, + ); - WP_CLI::line( (string) json_encode( $info ) ); + WP_CLI::line( json_encode( $info ) ); } else { - /** - * @var string $cfg_file_path - */ - $cfg_file_path = get_cfg_var( 'cfg_file_path' ); WP_CLI::line( "OS:\t" . $system_os ); WP_CLI::line( "Shell:\t" . $shell ); WP_CLI::line( "PHP binary:\t" . $php_bin ); WP_CLI::line( "PHP version:\t" . PHP_VERSION ); - WP_CLI::line( "PHP memory limit:\t" . $memory_limit ); - WP_CLI::line( "php.ini used:\t" . $cfg_file_path ); - WP_CLI::line( "MySQL binary:\t" . Utils\get_mysql_binary_path() ); - WP_CLI::line( "MySQL version:\t" . Utils\get_mysql_version() ); - WP_CLI::line( "SQL modes:\t" . implode( ',', Utils\get_sql_modes() ) ); + WP_CLI::line( "php.ini used:\t" . get_cfg_var( 'cfg_file_path' ) ); WP_CLI::line( "WP-CLI root dir:\t" . WP_CLI_ROOT ); WP_CLI::line( "WP-CLI vendor dir:\t" . WP_CLI_VENDOR_DIR ); WP_CLI::line( "WP_CLI phar path:\t" . ( defined( 'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH : '' ) ); WP_CLI::line( "WP-CLI packages dir:\t" . $packages_dir ); - WP_CLI::line( "WP-CLI cache dir:\t" . Utils\get_cache_dir() ); WP_CLI::line( "WP-CLI global config:\t" . $runner->global_config_path ); WP_CLI::line( "WP-CLI project config:\t" . $runner->project_config_path ); WP_CLI::line( "WP-CLI version:\t" . WP_CLI_VERSION ); } - - // Emit a warning if the memory limit is set to a low value. - $this->check_memory_limit( $memory_limit ); - } - - /** - * Checks if the PHP memory limit is too low and emits a warning if needed. - * - * @param string $memory_limit The current memory limit value from ini_get(). - */ - private function check_memory_limit( $memory_limit ) { - // If memory limit is -1 (unlimited), no warning needed. - if ( '-1' === $memory_limit ) { - return; - } - - // Convert memory limit string (e.g., "256M", "1G") to bytes. - $limit_bytes = $this->convert_to_bytes( $memory_limit ); - - // Warn if limit is below 512M. - // This is a reasonable threshold for CLI operations. - if ( $limit_bytes > 0 && $limit_bytes < self::MEMORY_LIMIT_WARNING_THRESHOLD ) { - WP_CLI::warning( - sprintf( - 'PHP memory limit is set to %s. This may be too low for some WP-CLI operations. Consider increasing it to at least 512M or setting it to -1 (unlimited) for CLI usage.', - $memory_limit - ) - ); - } - } - - /** - * Converts a memory limit string to bytes. - * - * @param string $value The memory limit value (e.g., "256M", "1G", "512K", "2.5G"). - * @return int The value in bytes, or -1 if unlimited. - */ - private function convert_to_bytes( $value ) { - $value = trim( $value ); - - if ( '-1' === $value ) { - return -1; - } - - // Handle empty string or invalid values. - if ( empty( $value ) ) { - return 0; - } - - $last = strtolower( $value[ strlen( $value ) - 1 ] ); - - // Extract numeric value before converting. - if ( ! is_numeric( $last ) ) { - $numeric_value = (float) substr( $value, 0, -1 ); - } else { - $numeric_value = (float) $value; - $last = ''; - } - - switch ( $last ) { - case 'g': - $numeric_value *= 1024; - // Fall through. - case 'm': - $numeric_value *= 1024; - // Fall through. - case 'k': - $numeric_value *= 1024; - } - - return (int) $numeric_value; } /** - * Checks to see if there is a newer version of WP-CLI available. + * Check to see if there is a newer version of WP-CLI available. * - * Queries the GitHub releases API. Returns available versions if there are + * Queries the Github releases API. Returns available versions if there are * updates available, or success message if using the latest release. * - * Unauthenticated requests to the GitHub API are rate limited to 60 per hour - * per IP address. If you are experiencing rate limit issues, you can generate - * a GitHub personal access token and set the GITHUB_TOKEN environment variable - * before running this command. Authenticated requests have a higher rate limit - * of 5,000 per hour. The token only needs public repository read access (no - * specific scopes required for public data). - * * ## OPTIONS * * [--patch] @@ -301,7 +166,7 @@ private function convert_to_bytes( $value ) { * : Prints the value of a single field for each update. * * [--fields=] - * : Limit the output to specific object fields. Defaults to version,update_type,package_url,status,requires_php. + * : Limit the output to specific object fields. Defaults to version,update_type,package_url. * * [--format=] * : Render output in a particular format. @@ -329,32 +194,25 @@ private function convert_to_bytes( $value ) { * | 0.24.1 | patch | https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar | * +---------+-------------+-------------------------------------------------------------------------------+ * - * # Check for update using a GitHub token to increase rate limit. - * $ GITHUB_TOKEN=ghp_... wp cli check-update - * Success: WP-CLI is at the latest version. - * * @subcommand check-update - * - * @param string[] $args Positional arguments. Unused. - * @param array{patch?: bool, minor?: bool, major?: bool, field?: string, fields?: string, format: string} $assoc_args Associative arguments. */ - public function check_update( $args, $assoc_args ) { + public function check_update( $_, $assoc_args ) { $updates = $this->get_updates( $assoc_args ); if ( $updates ) { - $formatter = new Formatter( + $formatter = new \WP_CLI\Formatter( $assoc_args, - [ 'version', 'update_type', 'package_url', 'status', 'requires_php' ] + array( 'version', 'update_type', 'package_url' ) ); $formatter->display_items( $updates ); - } elseif ( empty( $assoc_args['format'] ) || 'table' === $assoc_args['format'] ) { + } elseif ( empty( $assoc_args['format'] ) || 'table' == $assoc_args['format'] ) { $update_type = $this->get_update_type_str( $assoc_args ); WP_CLI::success( "WP-CLI is at the latest{$update_type}version." ); } } /** - * Updates WP-CLI to the latest release. + * Update WP-CLI to the latest release. * * Default behavior is to check the releases API for the newest stable * version, and prompt if one is available. @@ -368,13 +226,6 @@ public function check_update( $args, $assoc_args ) { * * Only works for the Phar installation mechanism. * - * Unauthenticated requests to the GitHub API are rate limited to 60 per hour - * per IP address. If you are experiencing rate limit issues, you can generate - * a GitHub personal access token and set the GITHUB_TOKEN environment variable - * before running this command. Authenticated requests have a higher rate limit - * of 5,000 per hour. The token only needs public repository read access (no - * specific scopes required for public data). - * * ## OPTIONS * * [--patch] @@ -395,39 +246,21 @@ public function check_update( $args, $assoc_args ) { * [--yes] * : Do not prompt for confirmation. * - * [--insecure] - * : Retry without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack. - * * ## EXAMPLES * * # Update CLI. * $ wp cli update - * You are currently using WP-CLI version 0.24.0. Would you like to update to 0.24.1? [y/n] y - * Downloading from https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar... - * New version works. Proceeding to replace. - * Success: Updated WP-CLI to 0.24.1. - * - * # Update CLI using a GitHub token to increase rate limit. - * $ GITHUB_TOKEN=ghp_... wp cli update - * You are currently using WP-CLI version 0.24.0. Would you like to update to 0.24.1? [y/n] y + * You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y * Downloading from https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar... * New version works. Proceeding to replace. * Success: Updated WP-CLI to 0.24.1. - * - * @param string[] $args Positional arguments. Unused. - * @param array{patch?: bool, minor?: bool, major?: bool, stable?: bool, nightly?: bool, yes?: bool, insecure?: bool} $assoc_args Associative arguments. */ - public function update( $args, $assoc_args ) { - if ( ! Path::inside_phar() ) { + public function update( $_, $assoc_args ) { + if ( ! Utils\inside_phar() ) { WP_CLI::error( 'You can only self-update Phar files.' ); } - /** - * @var string[] $argv - */ - $argv = $_SERVER['argv']; - - $old_phar = (string) realpath( $argv[0] ); + $old_phar = realpath( $_SERVER['argv'][0] ); if ( ! is_writable( $old_phar ) ) { WP_CLI::error( sprintf( '%s is not writable by current user.', $old_phar ) ); @@ -436,64 +269,60 @@ public function update( $args, $assoc_args ) { } if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { - WP_CLI::confirm( sprintf( 'You are currently using WP-CLI version %s. Would you like to update to the latest nightly version?', WP_CLI_VERSION ), $assoc_args ); + WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to the latest nightly?', WP_CLI_VERSION ), $assoc_args ); $download_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar'; - $md5_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar.md5'; - $sha512_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar.sha512'; + $md5_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar.md5'; } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { - WP_CLI::confirm( sprintf( 'You are currently using WP-CLI version %s. Would you like to update to the latest stable release?', WP_CLI_VERSION ), $assoc_args ); + WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to the latest stable release?', WP_CLI_VERSION ), $assoc_args ); $download_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'; - $md5_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.md5'; - $sha512_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha512'; + $md5_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.md5'; } else { $updates = $this->get_updates( $assoc_args ); - /** - * @phpstan-var UpdateOffer|null $newest - */ - $newest = $this->array_find( - $updates, - static function ( $update ) { - return 'available' === $update['status']; - } - ); - - if ( ! $newest ) { + if ( empty( $updates ) ) { $update_type = $this->get_update_type_str( $assoc_args ); WP_CLI::success( "WP-CLI is at the latest{$update_type}version." ); return; } + $newest = $updates[0]; + WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to %s?', WP_CLI_VERSION, $newest['version'] ), $assoc_args ); $download_url = $newest['package_url']; - $md5_url = str_replace( '.phar', '.phar.md5', $download_url ); - $sha512_url = str_replace( '.phar', '.phar.sha512', $download_url ); + $md5_url = str_replace( '.phar', '.phar.md5', $download_url ); } WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) ); - $temp = Utils\get_temp_dir() . uniqid( 'wp_', true ) . '.phar'; + $temp = \WP_CLI\Utils\get_temp_dir() . uniqid( 'wp_', true ) . '.phar'; - $headers = []; - $options = [ - 'timeout' => 600, // 10 minutes ought to be enough for everybody. + $headers = array(); + $options = array( + 'timeout' => 600, // 10 minutes ought to be enough for everybody. 'filename' => $temp, - 'insecure' => (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ), - ]; + ); Utils\http_request( 'GET', $download_url, null, $headers, $options ); - unset( $options['filename'] ); - - $this->validate_hashes( $temp, $sha512_url, $md5_url ); + $md5_response = Utils\http_request( 'GET', $md5_url ); + if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { + WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$md5_response->status_code})." ); + } + $md5_file = md5_file( $temp ); + $release_hash = trim( $md5_response->body ); + if ( $md5_file === $release_hash ) { + WP_CLI::log( 'md5 hash verified: ' . $release_hash ); + } else { + WP_CLI::error( "md5 hash for download ({$md5_file}) is different than the release hash ({$release_hash})." ); + } $allow_root = WP_CLI::get_runner()->config['allow-root'] ? '--allow-root' : ''; - $php_binary = escapeshellarg( Utils\get_php_binary() ); - $process = Process::create( "{$php_binary} $temp --info {$allow_root}" ); - $result = $process->run(); - if ( 0 !== $result->return_code || false === stripos( $result->stdout, 'WP-CLI version' ) ) { + $php_binary = Utils\get_php_binary(); + $process = WP_CLI\Process::create( "{$php_binary} $temp --info {$allow_root}" ); + $result = $process->run(); + if ( 0 !== $result->return_code || false === stripos( $result->stdout, 'WP-CLI version:' ) ) { $multi_line = explode( PHP_EOL, $result->stderr ); WP_CLI::error_multi_line( $multi_line ); WP_CLI::error( 'The downloaded PHAR is broken, try running wp cli update again.' ); @@ -507,172 +336,52 @@ static function ( $update ) { WP_CLI::error( sprintf( 'Cannot chmod %s.', $temp ) ); } - $temp = realpath( $temp ) ?: $temp; - class_exists( '\cli\Colors' ); // This autoloads \cli\Colors - after we move the file we no longer have access to this class. - $this->replace_current_phar( $temp, $old_phar ); + if ( false === rename( $temp, $old_phar ) ) { + WP_CLI::error( sprintf( 'Cannot move %s to %s', $temp, $old_phar ) ); + } - if ( Utils\get_flag_value( $assoc_args, 'nightly', false ) ) { + if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { $updated_version = 'the latest nightly release'; - } elseif ( Utils\get_flag_value( $assoc_args, 'stable', false ) ) { + } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { $updated_version = 'the latest stable release'; } else { - $updated_version = isset( $newest['version'] ) ? $newest['version'] : ''; + $updated_version = $newest['version']; } WP_CLI::success( sprintf( 'Updated WP-CLI to %s.', $updated_version ) ); } - /** - * Replaces the current Phar with the newly downloaded one. - * - * @param string $temp Path to the newly downloaded Phar. - * @param string $current_phar Path to the current Phar. - */ - private function replace_current_phar( $temp, $current_phar ) { - if ( Utils\is_windows() ) { - $bak_file = $current_phar . '.bak'; - @unlink( $bak_file ); - if ( file_exists( $bak_file ) ) { - @unlink( $temp ); - WP_CLI::error( sprintf( 'Cannot remove existing backup %s.', $bak_file ) ); - } - - if ( ! @rename( $current_phar, $bak_file ) ) { - $rename_error = error_get_last(); - @unlink( $temp ); - WP_CLI::error( - sprintf( - 'Cannot rename %s to backup %s%s', - $current_phar, - $bak_file, - isset( $rename_error['message'] ) ? ': ' . $rename_error['message'] : '.' - ) - ); - } - - if ( ! @rename( $temp, $current_phar ) ) { - $move_error = error_get_last(); - $revert_succeeded = @rename( $bak_file, $current_phar ); // Revert backup. - @unlink( $temp ); // Cleanup. - - $message = sprintf( - 'Cannot move %s to %s%s', - $temp, - $current_phar, - isset( $move_error['message'] ) ? ': ' . $move_error['message'] : '.' - ); - - if ( $revert_succeeded ) { - $message .= ' The original Phar was successfully restored.'; - } else { - $revert_error = error_get_last(); - $message .= sprintf( - ' Additionally, restoring the original Phar from backup failed%s. The backup file remains at %s for manual recovery.', - isset( $revert_error['message'] ) ? ': ' . $revert_error['message'] : '.', - $bak_file - ); - } - - WP_CLI::error( $message ); - } - - @unlink( $bak_file ); // Silently try to remove, will fail if still locked by current process. - } elseif ( ! @rename( $temp, $current_phar ) ) { - $move_error = error_get_last(); - @unlink( $temp ); // Cleanup. - WP_CLI::error( - sprintf( - 'Cannot move %s to %s%s', - $temp, - $current_phar, - isset( $move_error['message'] ) ? ': ' . $move_error['message'] : '.' - ) - ); - } - } - - /** - * @param string $file Release file path. - * @param string $sha512_url URL to sha512 hash. - * @param string $md5_url URL to md5 hash. - * - * @throws \WP_CLI\ExitException - */ - private function validate_hashes( $file, $sha512_url, $md5_url ): void { - $algos = [ - 'sha512' => $sha512_url, - 'md5' => $md5_url, - ]; - - foreach ( $algos as $algo => $url ) { - $response = Utils\http_request( 'GET', $url ); - if ( '20' !== substr( (string) $response->status_code, 0, 2 ) ) { - WP_CLI::log( "Couldn't access $algo hash for release (HTTP code {$response->status_code})." ); - continue; - } - - $file_hash = hash_file( $algo, $file ); - - $release_hash = trim( $response->body ); - if ( $file_hash === $release_hash ) { - WP_CLI::log( "$algo hash verified: $release_hash" ); - return; - } else { - WP_CLI::error( "$algo hash for download ($file_hash) is different than the release hash ($release_hash)." ); - } - } - - WP_CLI::error( 'Release hash verification failed.' ); - } - /** * Returns update information. */ private function get_updates( $assoc_args ) { $url = 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100'; - $options = [ - 'timeout' => 30, - 'insecure' => (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ), - ]; + $options = array( + 'timeout' => 30, + ); - $headers = [ + $headers = array( 'Accept' => 'application/json', - ]; - - $github_token = getenv( 'GITHUB_TOKEN' ); - if ( false !== $github_token ) { + ); + if ( $github_token = getenv( 'GITHUB_TOKEN' ) ) { $headers['Authorization'] = 'token ' . $github_token; } $response = Utils\http_request( 'GET', $url, null, $headers, $options ); if ( ! $response->success || 200 !== $response->status_code ) { - $error_message = sprintf( 'Failed to get latest version (HTTP code %d).', $response->status_code ); - if ( 403 === $response->status_code ) { - $error_message .= ' This is due to GitHub API rate limiting.'; - if ( false === $github_token ) { - $error_message .= ' Try using a GITHUB_TOKEN environment variable to authenticate with GitHub and get a higher rate limit.'; - $error_message .= ' See https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting for more information.'; - } - } - WP_CLI::error( $error_message ); + WP_CLI::error( sprintf( 'Failed to get latest version (HTTP code %d).', $response->status_code ) ); } - /** - * @phpstan-var GitHubRelease[] $release_data - */ - $release_data = json_decode( $response->body, false ); - - $updates = [ - 'major' => false, - 'minor' => false, - 'patch' => false, - ]; - - $updates_unavailable = []; + $release_data = json_decode( $response->body ); + $updates = array( + 'major' => false, + 'minor' => false, + 'patch' => false, + ); foreach ( $release_data as $release ) { // Get rid of leading "v" if there is one set. @@ -682,74 +391,19 @@ private function get_updates( $assoc_args ) { } $update_type = Utils\get_named_sem_ver( $release_version, WP_CLI_VERSION ); - if ( ! $update_type ) { continue; } - // Release is older than one we already have on file. if ( ! empty( $updates[ $update_type ] ) && ! Comparator::greaterThan( $release_version, $updates[ $update_type ]['version'] ) ) { continue; } - $package_url = null; - - /** - * WP-CLI manifest.json data. - * - * @var object{requires_php?: string}|null $manifest_data - */ - $manifest_data = null; - - foreach ( $release->assets as $asset ) { - if ( ! isset( $asset->browser_download_url ) ) { - continue; - } - - if ( substr( $asset->browser_download_url, - strlen( '.phar' ) ) === '.phar' ) { - $package_url = $asset->browser_download_url; - } - - // The manifest.json file, if it exists, contains information about PHP version requirements and similar. - if ( substr( $asset->browser_download_url, - strlen( 'manifest.json' ) ) === 'manifest.json' ) { - $response = Utils\http_request( 'GET', $asset->browser_download_url, null, $headers, $options ); - - if ( $response->success ) { - /** - * WP-CLI manifest.json data. - * - * @var object{requires_php?: string}|null $manifest_data - */ - $manifest_data = json_decode( $response->body, false ); - } - } - } - - if ( ! $package_url ) { - continue; - } - - // Release requires a newer version of PHP. - if ( - isset( $manifest_data->requires_php ) && - ! Comparator::greaterThanOrEqualTo( PHP_VERSION, $manifest_data->requires_php ) - ) { - $updates_unavailable[] = [ - 'version' => $release_version, - 'update_type' => $update_type, - 'package_url' => $release->assets[0]->browser_download_url, - 'status' => 'unavailable', - 'requires_php' => $manifest_data->requires_php, - ]; - } else { - $updates[ $update_type ] = [ - 'version' => $release_version, - 'update_type' => $update_type, - 'package_url' => $release->assets[0]->browser_download_url, - 'status' => 'available', - 'requires_php' => isset( $manifest_data->requires_php ) ? $manifest_data->requires_php : '', - ]; - } + $updates[ $update_type ] = array( + 'version' => $release_version, + 'update_type' => $update_type, + 'package_url' => $release->assets[0]->browser_download_url, + ); } foreach ( $updates as $type => $value ) { @@ -758,89 +412,33 @@ private function get_updates( $assoc_args ) { } } - foreach ( [ 'major', 'minor', 'patch' ] as $type ) { - if ( true === Utils\get_flag_value( $assoc_args, $type ) ) { - return ! empty( $updates[ $type ] ) ? [ $updates[ $type ] ] : false; + foreach ( array( 'major', 'minor', 'patch' ) as $type ) { + if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, $type ) ) { + return ! empty( $updates[ $type ] ) ? array( $updates[ $type ] ) : false; } } if ( empty( $updates ) && preg_match( '#-alpha-(.+)$#', WP_CLI_VERSION, $matches ) ) { $version_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/NIGHTLY_VERSION'; - $response = Utils\http_request( 'GET', $version_url, null, [], $options ); + $response = Utils\http_request( 'GET', $version_url ); if ( ! $response->success || 200 !== $response->status_code ) { WP_CLI::error( sprintf( 'Failed to get current nightly version (HTTP code %d)', $response->status_code ) ); } $nightly_version = trim( $response->body ); - - if ( WP_CLI_VERSION !== $nightly_version ) { - $manifest_data = null; - - // The manifest.json file, if it exists, contains information about PHP version requirements and similar. - $response = Utils\http_request( 'GET', 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.manifest.json', null, $headers, $options ); - - if ( $response->success ) { - /** - * WP-CLI manifest.json data. - * - * @var object{requires_php?: string}|null $manifest_data - */ - $manifest_data = json_decode( $response->body ); - } - - // Release requires a newer version of PHP. - if ( - isset( $manifest_data->requires_php ) && - ! Comparator::greaterThanOrEqualTo( PHP_VERSION, $manifest_data->requires_php ) - ) { - $updates_unavailable[] = [ - 'version' => $nightly_version, - 'update_type' => 'nightly', - 'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar', - 'status' => 'unavailable', - 'requires_php' => $manifest_data->requires_php, - ]; - } else { - $updates['nightly'] = [ - 'version' => $nightly_version, - 'update_type' => 'nightly', - 'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar', - 'status' => 'available', - 'requires_php' => isset( $manifest_data->requires_php ) ? $manifest_data->requires_php : '', - ]; - } - } - } - - return array_merge( $updates_unavailable, array_values( $updates ) ); - } - - /** - * Returns the the first element of the passed array for which the - * callback returns true. - * - * Polyfill for the `array_find()` function introduced in PHP 8.3. - * - * @param array $arr Array to search. - * @param callable $callback The callback function for each element in the array. - * @return mixed First array element for which the callback returns true, null otherwise. - */ - private function array_find( $arr, $callback ) { - if ( function_exists( '\array_find' ) ) { - // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_findFound - return \array_find( $arr, $callback ); - } - - foreach ( $arr as $key => $value ) { - if ( $callback( $value, $key ) ) { - return $value; + if ( WP_CLI_VERSION != $nightly_version ) { + $updates['nightly'] = array( + 'version' => $nightly_version, + 'update_type' => 'nightly', + 'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar', + ); } } - return null; + return array_values( $updates ); } /** - * Dumps the list of global parameters, as JSON or in var_export format. + * Dump the list of global parameters, as JSON or in var_export format. * * ## OPTIONS * @@ -876,11 +474,11 @@ private function array_find( $arr, $callback ) { * @subcommand param-dump */ public function param_dump( $_, $assoc_args ) { - $spec = WP_CLI::get_configurator()->get_spec(); + $spec = \WP_CLI::get_configurator()->get_spec(); - if ( Utils\get_flag_value( $assoc_args, 'with-values' ) ) { - $config = WP_CLI::get_configurator()->to_array(); - // Copy current config values to $spec. + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'with-values' ) ) { + $config = \WP_CLI::get_configurator()->to_array(); + // Copy current config values to $spec foreach ( $spec as $key => $value ) { $current = null; if ( isset( $config[0][ $key ] ) ) { @@ -890,7 +488,7 @@ public function param_dump( $_, $assoc_args ) { } } - if ( 'var_export' === Utils\get_flag_value( $assoc_args, 'format' ) ) { + if ( 'var_export' === \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) ) { var_export( $spec ); } else { echo json_encode( $spec ); @@ -898,7 +496,7 @@ public function param_dump( $_, $assoc_args ) { } /** - * Dumps the list of installed commands, as JSON. + * Dump the list of installed commands, as JSON. * * ## EXAMPLES * @@ -913,7 +511,7 @@ public function cmd_dump() { } /** - * Generates tab completion strings. + * Generate tab completion strings. * * ## OPTIONS * @@ -931,18 +529,57 @@ public function cmd_dump() { * eval-file */ public function completions( $_, $assoc_args ) { - $line = substr( $assoc_args['line'], 0, $assoc_args['point'] ); - $compl = new Completions( $line ); + $line = substr( $assoc_args['line'], 0, $assoc_args['point'] ); + $compl = new \WP_CLI\Completions( $line ); $compl->render(); } + /** + * List available WP-CLI aliases. + * + * Aliases are shorthand references to WordPress installs. For instance, + * `@dev` could refer to a development install and `@prod` could refer to + * a production install. This command gives you visibility in what + * registered aliases you have available. + * + * ## OPTIONS + * + * [--format=] + * : Render output in a particular format. + * --- + * default: yaml + * options: + * - yaml + * - json + * --- + * + * ## EXAMPLES + * + * # List all available aliases. + * $ wp cli alias + * --- + * @all: Run command against every registered alias. + * @prod: + * ssh: runcommand@runcommand.io~/webapps/production + * @dev: + * ssh: vagrant@192.168.50.10/srv/www/runcommand.dev + * @both: + * - @prod + * - @dev + * + * @alias aliases + */ + public function alias( $_, $assoc_args ) { + WP_CLI::print_value( WP_CLI::get_runner()->aliases, $assoc_args ); + } + /** * Get a string representing the type of update being checked for. */ private function get_update_type_str( $assoc_args ) { $update_type = ' '; - foreach ( [ 'major', 'minor', 'patch' ] as $type ) { - if ( true === Utils\get_flag_value( $assoc_args, $type ) ) { + foreach ( array( 'major', 'minor', 'patch' ) as $type ) { + if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, $type ) ) { $update_type = ' ' . $type . ' '; break; } @@ -973,14 +610,6 @@ private function get_update_type_str( $assoc_args ) { * $ echo $? * 1 * - * # Install a WP-CLI package if not already installed - * $ if ! $(wp cli has-command doctor); then wp package install wp-cli/doctor-command; fi - * Installing package wp-cli/doctor-command (dev-main || dev-master || dev-trunk) - * Updating /home/person/.wp-cli/packages/composer.json to require the package... - * Using Composer to install the package... - * --- - * Success: Package installed. - * * @subcommand has-command * * @when after_wp_load diff --git a/php/commands/src/Help_Command.php b/php/commands/src/Help_Command.php deleted file mode 100644 index 116a4da33..000000000 --- a/php/commands/src/Help_Command.php +++ /dev/null @@ -1,537 +0,0 @@ -...] - * : Get help on a specific command. - * - * [--full] - * : Show the full help, including help for all subcommands. - * - * ## EXAMPLES - * - * # get help for `core` command - * wp help core - * - * # get help for `core download` subcommand - * wp help core download - * - * # get full help for `core`, including all subcommands - * wp help core --full - * - * @param string[] $args - * @param array $assoc_args - */ - public function __invoke( $args, $assoc_args ) { - $r = WP_CLI::get_runner()->find_command_to_run( $args, Utils\get_env_or_config( 'WP_CLI_AUTOCORRECT' ) ? 'auto' : 'confirm' ); - - if ( is_array( $r ) ) { - list( $command ) = $r; - - if ( ! empty( $assoc_args['full'] ) ) { - $out = self::get_help_full( $command ); - self::pass_through_pager( $out ); - } else { - self::show_help( $command ); - } - exit; - } - } - - private static function show_help( $command ) { - self::pass_through_pager( self::get_help_as_string( $command ) ); - } - - private static function rewrap_param_desc( $matches ) { - $param = $matches[1]; - $desc = self::indent( "\t\t", $matches[2] ); - return "\t$param\n$desc\n\n"; - } - - private static function indent( $whitespace, $text ) { - $lines = explode( "\n", $text ); - foreach ( $lines as &$line ) { - $line = $whitespace . $line; - } - return implode( "\n", $lines ); - } - - /** - * Locate an executable binary or command by name using a platform-appropriate detector. - * - * On Windows, this uses `where`, and on POSIX systems it uses `command -v`. - * This may not work accurately in PowerShell. - * - * @param string $binary Name of the binary or command to be found. - * @return bool True if this command has determined that the binary or other command exists, false otherwise. - */ - public static function binary_exists( $binary ) { - if ( Utils\is_windows() ) { - // This may not work in PowerShell; see https://stackoverflow.com/a/304447 - // If this needs to be adjusted to use 'where.exe' for PowerShell, - // then we will need to add a way of detecting whether wp-cli is running in PowerShell. - $detector = 'where'; - } else { - // POSIX method to detect whether a command exists - // This sometimes detects aliases. - $detector = 'command -v'; - } - - $result = Process::create( $detector . ' ' . escapeshellarg( $binary ), null, null )->run(); - - if ( 0 !== $result->return_code ) { - // We could not reliably determine that the binary exists - return false; - } else { - // POSIX binaries: command -v will return the path and exit 0 - // aliases: command -v may return the alias command and exit 0 - return true; - } - } - - /** - * Determine whether to use `less` or `more` as a pager - * - * This caches the determined pager. - * - * @return string The command to use for the pager. Defaults to `more`. - */ - public static function locate_pager() { - static $pager = null; - - if ( empty( $pager ) ) { - if ( self::binary_exists( 'less' ) ) { - // less is not available in all systems - $pager = 'less -R'; - } else { - // more is part of the POSIX definition, and is also available on Windows. - $pager = 'more'; - } - } - - return $pager; - } - - /** - * Pass a given set of output through the system's terminal pager. - * - * @param string $out The output to be run through the pager. - * @return mixed Termination status of the pager as reported by https://www.php.net/manual/en/function.proc-close.php - */ - private static function pass_through_pager( $out ) { - - if ( ! Utils\check_proc_available( null /*context*/, true /*return*/ ) ) { - WP_CLI::line( $out ); - WP_CLI::debug( 'Warning: check_proc_available() failed in pass_through_pager().', 'help' ); - return -1; - } - - $pager = getenv( 'PAGER' ); - // if '' we should assume that the user has explicitly disabled the pager by setting `PAGER=` - if ( '' === $pager ) { - WP_CLI::line( $out ); - return 0; - } - if ( false === $pager ) { - $pager = self::locate_pager(); - } - - // If pager doesn't support ANSI colors, strip them from output. - // Common pagers that don't support colors: more, pg, cat, and less without -R. - // Pagers with color support typically use -R flag (less -R, most -R). - if ( ! preg_match( '/(-R|--RAW-CONTROL-CHARS|--raw-control-chars)/i', $pager ) - && preg_match( '/(^|[\s\/\\\\])(less|more|pg|cat)(\s|$)/i', $pager ) ) { - $out = \cli\Colors::decolorize( $out ); - WP_CLI::debug( 'Stripping ANSI color codes for pager without color support: ' . $pager, 'help' ); - } - - // For Windows 7 need to set code page to something other than Unicode (65001) to get around "Not enough memory." error with `more.com` on PHP 7.1+. - if ( 'more' === $pager && defined( 'PHP_WINDOWS_VERSION_MAJOR' ) && PHP_WINDOWS_VERSION_MAJOR < 10 ) { - // Note will also apply to Windows 8 (see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832.aspx) but probably harmless anyway. - $cp = (int) Utils\get_env_or_config( 'WP_CLI_WINDOWS_CODE_PAGE' ) ?: 1252; // Code page 1252 is the most used so probably the most compat. - sapi_windows_cp_set( $cp ); - } - - // Convert string to file handle. - $fd = fopen( 'php://temp', 'r+b' ); - if ( $fd ) { - fwrite( $fd, $out ); - rewind( $fd ); - } - - /** - * @var array $descriptorspec - */ - $descriptorspec = [ - 0 => $fd, - 1 => STDOUT, - 2 => STDERR, - ]; - - $process = Utils\proc_open_compat( $pager, $descriptorspec, $pipes ); - return $process ? proc_close( $process ) : -1; - } - - private static function get_initial_markdown( $command, $longdesc_with_links = null ) { - $name = implode( ' ', Dispatcher\get_path( $command ) ); - - $binding = [ - 'name' => $name, - 'shortdesc' => $command->get_shortdesc(), - ]; - - $binding['synopsis'] = "$name " . $command->get_synopsis(); - - $alias = $command->get_alias(); - if ( $alias ) { - $binding['alias'] = $alias; - } - $hook_name = $command->get_hook(); - $hook_description = $hook_name ? Utils\get_hook_description( $hook_name ) : null; - if ( $hook_description && 'after_wp_load' !== $hook_name ) { - if ( $command->can_have_subcommands() ) { - $binding['shortdesc'] .= "\n\nUnless overridden, these commands run on the '$hook_name' hook, $hook_description"; - } else { - $binding['shortdesc'] .= "\n\nThis command runs on the '$hook_name' hook, $hook_description"; - } - } - - // Add description paragraphs from longdesc to shortdesc for DESCRIPTION section - if ( null === $longdesc_with_links ) { - $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); - } - $longdesc_description = self::get_longdesc_description( $longdesc_with_links ); - if ( $longdesc_description ) { - $binding['shortdesc'] .= "\n\n" . $longdesc_description; - } - - if ( $command->can_have_subcommands() ) { - $binding['has-subcommands']['subcommands'] = self::render_subcommands( $command ); - } - - $binding['root_command'] = $command instanceof WP_CLI\Dispatcher\RootCommand; - - return Utils\mustache_render( 'man.mustache', $binding ); - } - - private static function render_subcommands( $command ) { - $subcommands = []; - foreach ( $command->get_subcommands() as $subcommand ) { - $disabled_reason = WP_CLI::get_runner()->get_command_disabled_reason( $subcommand ); - - $subcommands[ $subcommand->get_name() ] = [ - 'desc' => $subcommand->get_shortdesc(), - 'disabled_reason' => $disabled_reason, - ]; - } - - $max_len = self::get_max_len( array_keys( $subcommands ) ); - - $lines = []; - foreach ( $subcommands as $name => $data ) { - $desc = $data['desc']; - if ( false !== $data['disabled_reason'] ) { - $padded_name = str_pad( $name, $max_len ); - $colored_name = WP_CLI::colorize( '%r' . $name . '%n' ) . substr( $padded_name, strlen( $name ) ); - } else { - $colored_name = str_pad( $name, $max_len ); - } - - $lines[] = $colored_name . "\t\t\t" . $desc; - - if ( false !== $data['disabled_reason'] ) { - $indent = str_repeat( ' ', $max_len ) . "\t\t\t"; - $reason = $data['disabled_reason'] ?: 'disabled'; - $lines[] = $indent . WP_CLI::colorize( '%w' . $reason . '%n' ); - } - } - - return $lines; - } - - private static function get_help_full( $command ) { - $out = self::get_help_as_string( $command ); - - if ( $command->can_have_subcommands() ) { - foreach ( $command->get_subcommands() as $subcommand ) { - if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) { - continue; - } - $out .= "\n---\n\n" . self::get_help_full( $subcommand ); - } - } - - return $out; - } - - private static function get_help_as_string( $command ) { - $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); - $out = self::get_initial_markdown( $command, $longdesc_with_links ); - - $subcommands = ''; - $column_subpattern = '[ \t]+[^\t]+\t+'; - if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $matches, PREG_OFFSET_CAPTURE ) ) { - $subcommands = $matches[1][0]; - $subcommands_header = "## SUBCOMMANDS\n"; - $out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) ); - } - - $longdesc_sections = self::get_longdesc_sections( $longdesc_with_links ); - $out .= $longdesc_sections; - - $out = (string) preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', [ __CLASS__, 'rewrap_param_desc' ], $out ); - $out = (string) preg_replace( '/^((?! |\t|##).)/m', "\t$1", $out ); - - $tab = str_repeat( ' ', 2 ); - $out = str_replace( "\t", $tab, $out ); - - $wordwrap_width = Shell::columns(); - - $out = (string) preg_replace_callback( - '/^( *)([^\n]+)\n/m', - static function ( $matches ) use ( $wordwrap_width ) { - return $matches[1] . str_replace( "\n", "\n{$matches[1]}", wordwrap( $matches[2], $wordwrap_width - strlen( $matches[1] ) ) ) . "\n"; - }, - $out - ); - - if ( $subcommands ) { - $subcommands = (string) preg_replace_callback( - '/^(' . $column_subpattern . ')([^\n]+)\n/m', - static function ( $matches ) use ( $wordwrap_width, $tab ) { - $matches[1] = str_replace( "\t", $tab, $matches[1] ); - $matches[2] = str_replace( "\t", $tab, $matches[2] ); - $padding_len = strlen( $matches[1] ); - $padding = str_repeat( ' ', $padding_len ); - return $matches[1] . str_replace( "\n", "\n$padding", wordwrap( $matches[2], $wordwrap_width - $padding_len ) ) . "\n"; - }, - $subcommands - ); - - $out = str_replace( $subcommands_header, $subcommands, $out ); - } - - $out = (string) preg_replace( '/^## ([A-Z ]+)/m', WP_CLI::colorize( '%9\1%n' ), $out ); - - return $out; - } - - private static function get_max_len( $strings ) { - $max_len = 0; - foreach ( $strings as $str ) { - $len = strlen( $str ); - if ( $len > $max_len ) { - $max_len = $len; - } - } - - return $max_len; - } - - /** - * Parse reference links from longdescription. - * - * @param string $longdesc The longdescription from the `$command->get_longdesc()`. - * @return string The longdescription which has links as footnote. - */ - private static function parse_reference_links( $longdesc ) { - $description = self::extract_before_sections( $longdesc ); - - // Process if there is description text at the head of `$longdesc`. - if ( trim( $description ) ) { - $pattern = '/\[([^\]]+)\]\((https?:\/\/.+?)\)/'; - if ( self::supports_hyperlinks() ) { - $newdesc = (string) preg_replace_callback( - $pattern, - static function ( $matches ) { - return self::create_hyperlink( $matches[2], $matches[1] ); - }, - $description - ); - } else { - $links = []; // An array of URLs from the description. - $newdesc = (string) preg_replace_callback( - $pattern, - static function ( $matches ) use ( &$links ) { - static $count = 0; - $count++; - $links[] = $matches[2]; - return '[' . $matches[1] . '][' . $count . ']'; - }, - $description - ); - - $footnote = ''; - foreach ( $links as $i => $link ) { - $n = $i + 1; - $footnote .= '[' . $n . '] ' . $link . "\n"; - } - - if ( $footnote ) { - $newdesc = trim( $newdesc ) . "\n\n---\n" . $footnote; - } - } - - if ( trim( $description ) !== trim( $newdesc ) ) { - $longdesc = str_replace( trim( $description ), trim( $newdesc ), $longdesc ); - } - } - - return $longdesc; - } - - /** - * Determine whether the terminal supports OSC 8 hyperlinks. - * - * @return bool - */ - private static function supports_hyperlinks() { - $force_hyperlink = getenv( 'FORCE_HYPERLINK' ); - if ( false !== $force_hyperlink ) { - $force_hyperlink = trim( $force_hyperlink ); - if ( is_numeric( $force_hyperlink ) ) { - return (int) $force_hyperlink > 0; - } - - return '' !== $force_hyperlink; - } - - $is_stdout_tty = false; - if ( function_exists( 'stream_isatty' ) ) { - $is_stdout_tty = stream_isatty( STDOUT ); - } elseif ( function_exists( 'posix_isatty' ) ) { - $is_stdout_tty = posix_isatty( STDOUT ); - } - - if ( ! $is_stdout_tty || getenv( 'CI' ) ) { - return false; - } - - if ( false !== getenv( 'WT_SESSION' ) - || false !== getenv( 'KONSOLE_VERSION' ) - || false !== getenv( 'DOMTERM' ) - || 'xterm-kitty' === getenv( 'TERM' ) ) { - return true; - } - - $term_program = getenv( 'TERM_PROGRAM' ); - $term_program_version = getenv( 'TERM_PROGRAM_VERSION' ); - switch ( $term_program ) { - case 'iTerm.app': - return false !== $term_program_version && version_compare( $term_program_version, '3.1', '>=' ); - - case 'WezTerm': - if ( false !== $term_program_version && preg_match( '/^0-unstable-\d{4}-\d{2}-\d{2}$/', $term_program_version ) ) { - $date = substr( $term_program_version, strlen( '0-unstable-' ) ); - return $date >= '2020-06-20'; - } - if ( false !== $term_program_version && preg_match( '/^\d{8}/', $term_program_version ) ) { - $version = (int) substr( $term_program_version, 0, 8 ); - return $version >= 20200620; - } - return false; - - case 'vscode': - if ( false !== getenv( 'CURSOR_TRACE_ID' ) ) { - return true; - } - return false !== $term_program_version && version_compare( $term_program_version, '1.72', '>=' ); - - case 'ghostty': - case 'zed': - return true; - } - - $vte_version = getenv( 'VTE_VERSION' ); - if ( '0.50.0' === $vte_version ) { - return false; - } - if ( false !== $vte_version ) { - if ( preg_match( '/^(\d{3,4})$/', $vte_version, $matches ) ) { - $version = $matches[1]; - $minor = (int) substr( $version, 0, -2 ); - return $minor >= 50; - } - - if ( preg_match( '/^(\d+)\.(\d+)/', $vte_version, $matches ) ) { - $major = (int) $matches[1]; - $minor = (int) $matches[2]; - return $major > 0 || $minor >= 50; - } - } - - return in_array( getenv( 'TERM' ), [ 'alacritty', 'xterm-kitty' ], true ); - } - - /** - * Create an OSC 8 hyperlink. - * - * @param string $url URL for the hyperlink. - * @param string $text Link text. - * @return string - */ - private static function create_hyperlink( $url, $text ) { - $url = (string) preg_replace( '/[\x00-\x1F\x7F-\x9F]/', '', (string) $url ); - $text = (string) preg_replace( '/[\x00-\x1F\x7F-\x9F]/', '', (string) $text ); - return "\033]8;;{$url}\033\\{$text}\033]8;;\033\\"; - } - - /** - * Extract description paragraphs from longdesc (content before first section header). - * - * @param string $longdesc The longdescription from the command. - * @return string The description paragraphs only. - */ - private static function get_longdesc_description( $longdesc ) { - return trim( self::extract_before_sections( $longdesc ) ); - } - - /** - * Extract section content from longdesc (content from first section header onwards). - * - * @param string $longdesc The longdescription from the command. - * @return string The section content only (OPTIONS, EXAMPLES, etc.). - */ - private static function get_longdesc_sections( $longdesc ) { - $sections = ''; - $in_sections = false; - foreach ( explode( "\n", $longdesc ) as $line ) { - if ( ! $in_sections && 0 === strpos( $line, '##' ) ) { - $in_sections = true; - } - if ( $in_sections ) { - $sections .= $line . "\n"; - } - } - - return $sections; - } - - /** - * Extract content before first section header (##). - * - * @param string $text The text to extract from. - * @return string Content before first section header. - */ - private static function extract_before_sections( $text ) { - $before_sections = ''; - foreach ( explode( "\n", $text ) as $line ) { - if ( 0 === strpos( $line, '##' ) ) { - break; - } - $before_sections .= $line . "\n"; - } - - return $before_sections; - } -} diff --git a/php/compat.php b/php/compat.php deleted file mode 100644 index b3d9bbc7f..000000000 --- a/php/compat.php +++ /dev/null @@ -1 +0,0 @@ - [ +return array( + 'path' => array( 'runtime' => '=', - 'file' => '', - 'desc' => 'Path to the WordPress files.', - ], + 'file' => '', + 'desc' => 'Path to the WordPress files.', + ), - 'url' => [ + 'url' => array( 'runtime' => '=', - 'file' => '', - 'desc' => 'Pretend request came from given URL. In multisite, this argument is how the target site is specified.', - ], + 'file' => '', + 'desc' => 'Pretend request came from given URL. In multisite, this argument is how the target site is specified.', + ), - 'ssh' => [ + 'ssh' => array( 'runtime' => '=[:][@][:][]', - 'file' => '[:][@][:][]', - 'desc' => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "docker-compose-run", "vagrant").', - ], - - 'ssh-args' => [ - 'runtime' => '=', - 'file' => '', - 'desc' => 'Pass additional arguments to SSH (or other tools specified by --ssh scheme).', - 'multiple' => true, - 'default' => [], - ], + 'file' => '[:][@][:][]', + 'desc' => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "vagrant").', + ), - 'http' => [ + 'http' => array( 'runtime' => '=', - 'file' => '', - 'desc' => 'Perform operation against a remote WordPress installation over HTTP.', - ], + 'file' => '', + 'desc' => 'Perform operation against a remote WordPress install over HTTP.', + ), - 'blog' => [ + 'blog' => array( 'deprecated' => 'Use --url instead.', - 'runtime' => '=', - ], + 'runtime' => '=', + ), - 'user' => [ + 'user' => array( 'runtime' => '=', - 'file' => '', - 'desc' => 'Set the WordPress user.', - ], - - 'skip-plugins' => [ - 'runtime' => '[=]', - 'file' => '', - 'desc' => 'Skip loading all plugins, or a comma-separated list of plugins. Note: mu-plugins are still loaded.', + 'file' => '', + 'desc' => 'Set the WordPress user.', + ), + + 'skip-plugins' => array( + 'runtime' => '[=]', + 'file' => '', + 'desc' => 'Skip loading all or some plugins. Note: mu-plugins are still loaded.', 'default' => '', - ], + ), - 'skip-themes' => [ - 'runtime' => '[=]', - 'file' => '', - 'desc' => 'Skip loading all themes, or a comma-separated list of themes.', + 'skip-themes' => array( + 'runtime' => '[=]', + 'file' => '', + 'desc' => 'Skip loading all or some themes.', 'default' => '', - ], + ), - 'skip-packages' => [ - 'runtime' => '', - 'file' => '', - 'desc' => 'Skip loading all installed packages.', - 'default' => false, - ], + 'skip-packages' => array( + 'runtime' => '', + 'file' => '', + 'desc' => 'Skip loading all installed packages.', + 'default' => false, + ), - 'require' => [ - 'runtime' => '=', - 'file' => '', - 'desc' => 'Load PHP file before running the command (may be used more than once).', - 'multiple' => true, - 'default' => [], - ], - - 'exec' => [ - 'runtime' => '=', - 'file' => '', - 'desc' => 'Execute PHP code before running the command (may be used more than once).', + 'require' => array( + 'runtime' => '=', + 'file' => '', + 'desc' => 'Load PHP file before running the command (may be used more than once).', 'multiple' => true, - 'default' => [], - ], + 'default' => array(), + ), - 'context' => [ - 'runtime' => '=', - 'file' => '', - 'default' => 'auto', - 'desc' => 'Load WordPress in a given context.', - ], - - 'disabled_commands' => [ - 'file' => '', - 'default' => [], - 'multiple' => true, - 'desc' => '(Sub)commands to disable.', - ], + 'disabled_commands' => array( + 'file' => '', + 'default' => array(), + 'desc' => '(Sub)commands to disable.', + ), - 'color' => [ + 'color' => array( 'runtime' => true, - 'file' => '', + 'file' => '', 'default' => 'auto', - 'desc' => 'Whether to colorize the output.', - ], + 'desc' => 'Whether to colorize the output.', + ), - 'debug' => [ + 'debug' => array( 'runtime' => '[=]', - 'file' => '', + 'file' => '', 'default' => false, - 'desc' => 'Show all PHP errors and add verbosity to WP-CLI output. Built-in groups include: bootstrap, commandfactory, and help.', - ], + 'desc' => 'Show all PHP errors; add verbosity to WP-CLI bootstrap.', + ), - 'prompt' => [ + 'prompt' => array( 'runtime' => '[=]', - 'file' => false, + 'file' => false, 'default' => false, - 'desc' => 'Prompt the user to enter values for all command arguments, or a subset specified as comma-separated values.', - ], + 'desc' => 'Prompt the user to enter values for all command arguments, or a subset specified as comma-separated values.', + ), - 'quiet' => [ + 'quiet' => array( 'runtime' => '', - 'file' => '', + 'file' => '', 'default' => false, - 'desc' => 'Suppress informational messages.', - ], + 'desc' => 'Suppress informational messages.', + ), - 'apache_modules' => [ - 'file' => '', - 'desc' => 'List of Apache Modules that are to be reported as loaded.', + 'apache_modules' => array( + 'file' => '', + 'desc' => 'List of Apache Modules that are to be reported as loaded.', 'multiple' => true, - 'default' => [], - ], + 'default' => array(), + ), # --allow-root => (NOT RECOMMENDED) Allow wp-cli to run as root. This poses # a security risk, so you probably do not want to do this. - 'allow-root' => [ - 'file' => false, # Explicit. Just in case the default changes. + 'allow-root' => array( + 'file' => false, # Explicit. Just in case the default changes. 'runtime' => '', 'hidden' => true, - ], - - 'alias' => [ - 'runtime' => '=', - 'file' => '', - 'desc' => 'Name of the alias to use. Aliases can reference local WordPress installations or remote SSH connections. Aliases are defined in the wp-cli.yml file.', - 'multiple' => false, - 'default' => '', - ], - - 'assume-https' => [ - 'runtime' => '', - 'file' => '', - 'default' => false, - 'desc' => 'Set $_SERVER[\'HTTPS\'] to make WordPress treat the site as HTTPS. Use when WordPress is behind an HTTPS proxy or load balancer.', - ], + ), -]; +); diff --git a/php/dispatcher.php b/php/dispatcher.php index c86d10649..bd11a3927 100644 --- a/php/dispatcher.php +++ b/php/dispatcher.php @@ -5,16 +5,16 @@ /** * Get the path to a command, e.g. "core download" * - * @param Subcommand|CompositeCommand $command - * @return string[] + * @param WP_CLI\Dispatcher\Subcommand $command + * @return string */ function get_path( $command ) { - $path = []; + $path = array(); do { array_unshift( $path, $command->get_name() ); - $command = $command->get_parent(); - } while ( $command ); + } while ( $command = $command->get_parent() ); return $path; } + diff --git a/php/fallback-functions.php b/php/fallback-functions.php deleted file mode 100644 index b9605fcd6..000000000 --- a/php/fallback-functions.php +++ /dev/null @@ -1,11 +0,0 @@ -= 8 && ! function_exists( 'ini_set' ) ) { - function ini_set( $option, $value ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- This is a stub only. - return false; - } -} diff --git a/php/utils-wp.php b/php/utils-wp.php index f46fc8d03..b7ec062e9 100644 --- a/php/utils-wp.php +++ b/php/utils-wp.php @@ -4,20 +4,11 @@ namespace WP_CLI\Utils; -use ReflectionClass; -use ReflectionParameter; -use WP_CLI; -use WP_CLI\Path; -use WP_CLI\UpgraderSkin; - -/** - * @return void - */ function wp_not_installed() { global $wpdb, $table_prefix; if ( ! is_blog_installed() && ! defined( 'WP_INSTALLING' ) ) { - $tables = $wpdb->get_col( "SHOW TABLES LIKE '%_options'" ); - $found_prefixes = []; + $tables = $wpdb->get_col( "SHOW TABLES LIKE '%_options'" ); + $found_prefixes = array(); if ( count( $tables ) ) { foreach ( $tables as $table ) { $maybe_prefix = substr( $table, 0, - strlen( 'options' ) ); @@ -27,16 +18,15 @@ function wp_not_installed() { } } if ( count( $found_prefixes ) ) { - sort( $found_prefixes ); - $prefix_list = implode( ', ', $found_prefixes ); - $install_label = count( $found_prefixes ) > 1 ? 'installations' : 'installation'; - WP_CLI::error( + $prefix_list = implode( ', ', $found_prefixes ); + $install_label = count( $found_prefixes ) > 1 ? 'installs' : 'install'; + \WP_CLI::error( "The site you have requested is not installed.\n" . "Your table prefix is '{$table_prefix}'. Found {$install_label} with table prefix: {$prefix_list}.\n" . 'Or, run `wp core install` to create database tables.' ); } else { - WP_CLI::error( + \WP_CLI::error( "The site you have requested is not installed.\n" . 'Run `wp core install` to create database tables.' ); @@ -44,96 +34,64 @@ function wp_not_installed() { } } -// phpcs:disable WordPress.PHP.IniSet -- Intentional & correct usage. - -/** - * @return void - */ function wp_debug_mode() { - if ( WP_CLI::get_config( 'debug' ) ) { + if ( \WP_CLI::get_config( 'debug' ) ) { if ( ! defined( 'WP_DEBUG' ) ) { define( 'WP_DEBUG', true ); } - error_reporting( E_ALL & ~E_DEPRECATED ); + error_reporting( E_ALL & ~E_DEPRECATED & ~E_STRICT ); } else { - if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + if ( WP_DEBUG ) { error_reporting( E_ALL ); - if ( defined( 'WP_DEBUG_DISPLAY' ) ) { - ini_set( 'display_errors', WP_DEBUG_DISPLAY ? 1 : 0 ); + if ( WP_DEBUG_DISPLAY ) { + ini_set( 'display_errors', 1 ); + } elseif ( null !== WP_DEBUG_DISPLAY ) { + ini_set( 'display_errors', 0 ); } - if ( defined( 'WP_DEBUG_LOG' ) ) { - // @phpstan-ignore cast.useless - if ( in_array( strtolower( (string) WP_DEBUG_LOG ), [ 'true', '1' ], true ) ) { - $log_path = WP_CONTENT_DIR . '/debug.log'; - // @phpstan-ignore function.alreadyNarrowedType - } elseif ( is_string( WP_DEBUG_LOG ) ) { - $log_path = WP_DEBUG_LOG; - } else { - $log_path = false; - } - - if ( false !== $log_path ) { - ini_set( 'log_errors', 1 ); - ini_set( 'error_log', $log_path ); - } + if ( WP_DEBUG_LOG ) { + ini_set( 'log_errors', 1 ); + ini_set( 'error_log', WP_CONTENT_DIR . '/debug.log' ); } } else { error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR ); } - // wp_doing_ajax() might not be available. - // @phpstan-ignore phpstanWP.wpConstant.fetch if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { ini_set( 'display_errors', 0 ); } } - // XDebug already sends errors to STDERR. - ini_set( 'display_errors', function_exists( 'xdebug_debug_zval' ) ? false : 'stderr' ); + // XDebug already sends errors to STDERR + ini_set( 'display_errors', function_exists( 'xdebug_debug_zval' ) ? false : 'STDERR' ); } -// phpcs:enable -/** - * @return void - */ function replace_wp_die_handler() { \remove_filter( 'wp_die_handler', '_default_wp_die_handler' ); \add_filter( - 'wp_die_handler', - function () { - return __NAMESPACE__ . '\\wp_die_handler'; + 'wp_die_handler', function() { + return __NAMESPACE__ . '\\' . 'wp_die_handler'; } ); } -/** - * @return never - */ function wp_die_handler( $message ) { - if ( $message instanceof \WP_Error ) { - $text_message = $message->get_error_message(); - } else { - $text_message = $message; + $message = $message->get_error_message(); } - $text_message = wp_clean_error_message( $text_message ); + $message = wp_clean_error_message( $message ); - WP_CLI::error( $text_message ); + \WP_CLI::error( $message ); } /** * Clean HTML error message so suitable for text display. - * - * @param string $message - * @return string */ function wp_clean_error_message( $message ) { - $original_message = trim( $message ); - $message = $original_message; + $original_message = $message = trim( $message ); if ( preg_match( '|^\

(.+?)

|', $original_message, $matches ) ) { $message = $matches[1] . '.'; } @@ -141,94 +99,47 @@ function wp_clean_error_message( $message ) { $message .= ' ' . $matches[1]; } - $search_replace = [ - '' => '`', - '' => '`', - ]; - $message = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $message ); - $message = namespace\strip_tags( $message ); - $message = html_entity_decode( $message, ENT_COMPAT, 'UTF-8' ); + $search_replace = array( + '' => '`', + '' => '`', + ); + $message = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $message ); + $message = strip_tags( $message ); + $message = html_entity_decode( $message, ENT_COMPAT, 'UTF-8' ); return $message; } -/** - * @param string $since Version number. - * @param string $path File to include. - * @return void - */ +function wp_redirect_handler( $url ) { + \WP_CLI::warning( 'Some code is trying to do a URL redirect. Backtrace:' ); + + ob_start(); + debug_print_backtrace(); + fwrite( STDERR, ob_get_clean() ); + + return $url; +} + function maybe_require( $since, $path ) { if ( wp_version_compare( $since, '>=' ) ) { require $path; } } -/** - * @template T of \WP_Upgrader - * - * @param class-string $class_name Class name. - * @param bool $insecure Optional. Default false. - * @param \WP_Upgrader_Skin $skin. Optional. Upgrader skin. Default \WP_CLI\UpgraderSkin. - * - * @return T Upgrader instance. - * @throws \ReflectionException - */ -function get_upgrader( $class_name, $insecure = false, $skin = null ) { +function get_upgrader( $class ) { if ( ! class_exists( '\WP_Upgrader' ) ) { - if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ) ) { - include ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; - } + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } - if ( ! class_exists( '\WP_Upgrader_Skin' ) ) { - if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php' ) ) { - include ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php'; - } - } - - $uses_insecure_flag = false; - - $reflection = new ReflectionClass( $class_name ); - $constructor = $reflection->getConstructor(); - if ( $constructor ) { - $arguments = $constructor->getParameters(); - /** @var ReflectionParameter $argument */ - foreach ( $arguments as $argument ) { - if ( 'insecure' === $argument->name ) { - $uses_insecure_flag = true; - break; - } - } - } - - if ( $uses_insecure_flag ) { - /** - * @var T $result - */ - // TODO: Introduce custom upgrader interface supporting two arguments. - // @phpstan-ignore arguments.count - $result = new $class_name( $skin ?: new UpgraderSkin(), $insecure ); - - return $result; - } - - /** - * @var T $result - */ - $result = new $class_name( $skin ?: new UpgraderSkin() ); - - return $result; + return new $class( new \WP_CLI\UpgraderSkin ); } /** * Converts a plugin basename back into a friendly slug. - * - * @param string $basename - * @return string */ function get_plugin_name( $basename ) { if ( false === strpos( $basename, '/' ) ) { - $name = Path::basename( $basename, '.php' ); + $name = basename( $basename, '.php' ); } else { $name = dirname( $basename ); } @@ -236,16 +147,10 @@ function get_plugin_name( $basename ) { return $name; } -/** - * Determine whether a plugin is skipped. - * - * @param string $file - * @return bool - */ function is_plugin_skipped( $file ) { $name = get_plugin_name( str_replace( WP_PLUGIN_DIR . '/', '', $file ) ); - $skipped_plugins = WP_CLI::get_runner()->config['skip-plugins']; + $skipped_plugins = \WP_CLI::get_runner()->config['skip-plugins']; if ( true === $skipped_plugins ) { return true; } @@ -254,29 +159,17 @@ function is_plugin_skipped( $file ) { $skipped_plugins = explode( ',', $skipped_plugins ); } - return in_array( $name, array_filter( $skipped_plugins ), true ); + return in_array( $name, array_filter( $skipped_plugins ) ); } -/** - * Get theme name from path. - * - * @param string $path - * @return string - */ function get_theme_name( $path ) { - return Path::basename( $path ); + return basename( $path ); } -/** - * Determine whether a theme is skipped. - * - * @param string $path - * @return bool - */ function is_theme_skipped( $path ) { $name = get_theme_name( $path ); - $skipped_themes = WP_CLI::get_runner()->config['skip-themes']; + $skipped_themes = \WP_CLI::get_runner()->config['skip-themes']; if ( true === $skipped_themes ) { return true; } @@ -285,29 +178,28 @@ function is_theme_skipped( $path ) { $skipped_themes = explode( ',', $skipped_themes ); } - return in_array( $name, array_filter( $skipped_themes ), true ); + return in_array( $name, array_filter( $skipped_themes ) ); } /** - * Register the sidebar for unused widgets. - * Core does this in /wp-admin/widgets.php, which isn't helpful. - * - * @return void + * Register the sidebar for unused widgets + * Core does this in /wp-admin/widgets.php, which isn't helpful */ function wp_register_unused_sidebar() { register_sidebar( - [ - 'name' => __( 'Inactive Widgets' ), - 'id' => 'wp_inactive_widgets', - 'class' => 'inactive-sidebar', - 'description' => __( 'Drag widgets here to remove them from the sidebar but keep their settings.' ), + array( + 'name' => __( 'Inactive Widgets' ), + 'id' => 'wp_inactive_widgets', + 'class' => 'inactive-sidebar', + 'description' => __( 'Drag widgets here to remove them from the sidebar but keep their settings.' ), 'before_widget' => '', - 'after_widget' => '', - 'before_title' => '', - 'after_title' => '', - ] + 'after_widget' => '', + 'before_title' => '', + 'after_title' => '', + ) ); + } /** @@ -322,18 +214,16 @@ function wp_register_unused_sidebar() { function wp_get_cache_type() { global $_wp_using_ext_object_cache, $wp_object_cache; - $message = 'Unknown'; - if ( ! empty( $_wp_using_ext_object_cache ) ) { // Test for Memcached PECL extension memcached object cache (https://github.com/tollmanz/wordpress-memcached-backend) - if ( isset( $wp_object_cache->m ) && $wp_object_cache->m instanceof \Memcached ) { + if ( isset( $wp_object_cache->m ) && is_a( $wp_object_cache->m, 'Memcached' ) ) { $message = 'Memcached'; - // Test for Memcache PECL extension memcached object cache (https://wordpress.org/extend/plugins/memcached/) + // Test for Memcache PECL extension memcached object cache (http://wordpress.org/extend/plugins/memcached/) } elseif ( isset( $wp_object_cache->mc ) ) { $is_memcache = true; foreach ( $wp_object_cache->mc as $bucket ) { - if ( ! $bucket instanceof \Memcache && ! $bucket instanceof \Memcached ) { + if ( ! is_a( $bucket, 'Memcache' ) && ! is_a( $bucket, 'Memcached' ) ) { $is_memcache = false; } } @@ -342,68 +232,39 @@ function wp_get_cache_type() { $message = 'Memcache'; } - // Test for Xcache object cache (https://plugins.svn.wordpress.org/xcache/trunk/object-cache.php) - } elseif ( $wp_object_cache instanceof \XCache_Object_Cache ) { + // Test for Xcache object cache (http://plugins.svn.wordpress.org/xcache/trunk/object-cache.php) + } elseif ( is_a( $wp_object_cache, 'XCache_Object_Cache' ) ) { $message = 'Xcache'; - // Test for WinCache object cache (https://wordpress.org/extend/plugins/wincache-object-cache-backend/) + // Test for WinCache object cache (http://wordpress.org/extend/plugins/wincache-object-cache-backend/) } elseif ( class_exists( 'WinCache_Object_Cache' ) ) { $message = 'WinCache'; - // Test for APC object cache (https://wordpress.org/extend/plugins/apc/) + // Test for APC object cache (http://wordpress.org/extend/plugins/apc/) } elseif ( class_exists( 'APC_Object_Cache' ) ) { $message = 'APC'; - // Test for WP Redis (https://wordpress.org/plugins/wp-redis/) - } elseif ( isset( $wp_object_cache->redis ) && $wp_object_cache->redis instanceof \Redis ) { - $message = 'Redis'; - - // Test for Redis Object Cache (https://wordpress.org/plugins/redis-cache/) - } elseif ( method_exists( $wp_object_cache, 'redis_instance' ) && method_exists( $wp_object_cache, 'redis_status' ) ) { - $message = 'Redis'; - - // Test for Object Cache Pro (https://objectcache.pro/) - } elseif ( method_exists( $wp_object_cache, 'config' ) && method_exists( $wp_object_cache, 'connection' ) ) { + // Test for Redis Object Cache (https://github.com/alleyinteractive/wp-redis) + } elseif ( isset( $wp_object_cache->redis ) && is_a( $wp_object_cache->redis, 'Redis' ) ) { $message = 'Redis'; // Test for WP LCache Object cache (https://github.com/lcache/wp-lcache) - } elseif ( isset( $wp_object_cache->lcache ) && $wp_object_cache->lcache instanceof \LCache\Integrated ) { + } elseif ( isset( $wp_object_cache->lcache ) && is_a( $wp_object_cache->lcache, '\LCache\Integrated' ) ) { $message = 'WP LCache'; - // Test for WP-Stash (https://github.com/inpsyde/WP-Stash) - } elseif ( class_exists( 'Inpsyde\WpStash\WpStash' ) ) { - try { - $wp_stash = \Inpsyde\WpStash\WpStash::instance(); - if ( is_object( $wp_stash ) && method_exists( $wp_stash, 'driver' ) ) { - $driver = $wp_stash->driver(); - if ( is_object( $driver ) ) { - $message = 'WP-Stash (' . get_class( $driver ) . ')'; - } else { - $message = 'WP-Stash'; - } - } else { - $message = 'WP-Stash'; - } - } catch ( \Throwable $e ) { - // If WP-Stash fails to initialize, we can't determine the driver - $message = 'WP-Stash'; - } } elseif ( function_exists( 'w3_instance' ) ) { $config = w3_instance( 'W3_Config' ); + $message = 'Unknown'; if ( $config->get_boolean( 'objectcache.enabled' ) ) { $message = 'W3TC ' . $config->get_string( 'objectcache.engine' ); } - } - - // If still unknown, provide the class name for debugging - if ( 'Unknown' === $message && is_object( $wp_object_cache ) ) { - $message = 'Unknown: ' . get_class( $wp_object_cache ); + } else { + $message = 'Unknown'; } } else { $message = 'Default'; } - return $message; } @@ -416,39 +277,23 @@ function wp_get_cache_type() { * * @access public * @category System - * @deprecated 1.5.0 - * - * @return void */ function wp_clear_object_cache() { global $wpdb, $wp_object_cache; - $wpdb->queries = []; - - if ( function_exists( 'wp_cache_flush_runtime' ) && function_exists( 'wp_cache_supports' ) ) { - if ( wp_cache_supports( 'flush_runtime' ) ) { - wp_cache_flush_runtime(); - return; - } - } + $wpdb->queries = array(); // or define( 'WP_IMPORTING', true ); if ( ! is_object( $wp_object_cache ) ) { return; } - // The following are Memcached (Redux) plugin specific (see https://core.trac.wordpress.org/ticket/31463). - if ( isset( $wp_object_cache->group_ops ) ) { - $wp_object_cache->group_ops = []; - } - if ( isset( $wp_object_cache->stats ) ) { - $wp_object_cache->stats = []; - } - if ( isset( $wp_object_cache->memcache_debug ) ) { - $wp_object_cache->memcache_debug = []; - } - // Used by `WP_Object_Cache` also. - if ( isset( $wp_object_cache->cache ) ) { - $wp_object_cache->cache = []; + $wp_object_cache->group_ops = array(); + $wp_object_cache->stats = array(); + $wp_object_cache->memcache_debug = array(); + $wp_object_cache->cache = array(); + + if ( is_callable( $wp_object_cache, '__remoteset' ) ) { + $wp_object_cache->__remoteset(); // important } } @@ -457,138 +302,124 @@ function wp_clear_object_cache() { * * Interprets common command-line options into a resolved set of table names. * - * @param array $args Provided table names, or tables with wildcards. - * @param array $assoc_args Optional flags for groups of tables (e.g. --network) - * @return array + * @param array $args Provided table names, or tables with wildcards. + * @param array $assoc_args Optional flags for groups of tables (e.g. --network) + * @return array $tables */ -function wp_get_table_names( $args, $assoc_args = [] ) { +function wp_get_table_names( $args, $assoc_args = array() ) { global $wpdb; - // Abort if incompatible args supplied. - if ( get_flag_value( $assoc_args, 'base-tables-only' ) && get_flag_value( $assoc_args, 'views-only' ) ) { - WP_CLI::error( 'You cannot supply --base-tables-only and --views-only at the same time.' ); + // Prioritize any supplied $args as tables + if ( ! empty( $args ) ) { + $new_tables = array(); + $get_tables_for_glob = function( $glob ) { + global $wpdb; + static $all_tables = array(); + if ( ! $all_tables ) { + $all_tables = $wpdb->get_col( 'SHOW TABLES' ); + } + $tables = array(); + foreach ( $all_tables as $table ) { + if ( fnmatch( $glob, $table ) ) { + $tables[] = $table; + } + } + return $tables; + }; + foreach ( $args as $key => $table ) { + if ( false !== strpos( $table, '*' ) || false !== strpos( $table, '?' ) ) { + $expanded_tables = $get_tables_for_glob( $table ); + if ( empty( $expanded_tables ) ) { + \WP_CLI::error( "Couldn't find any tables matching: {$table}" ); + } + $new_tables = array_merge( $new_tables, $expanded_tables ); + } else { + $new_tables[] = $table; + } + } + return $new_tables; } - // Pre-load tables SQL query with Views restriction if needed. - if ( get_flag_value( $assoc_args, 'base-tables-only' ) ) { - $tables_sql = 'SHOW FULL TABLES WHERE Table_Type = "BASE TABLE"'; - - } elseif ( get_flag_value( $assoc_args, 'views-only' ) ) { - $tables_sql = 'SHOW FULL TABLES WHERE Table_Type = "VIEW"'; - + // Fall back to flag if no tables were passed + $table_type = 'WordPress'; + if ( get_flag_value( $assoc_args, 'network' ) ) { + $table_type = 'network'; + } + if ( get_flag_value( $assoc_args, 'all-tables-with-prefix' ) ) { + $table_type = 'all-tables-with-prefix'; } - if ( get_flag_value( $assoc_args, 'all-tables' ) ) { - if ( empty( $tables_sql ) ) { - $tables_sql = 'SHOW TABLES'; - } + $table_type = 'all-tables'; + } - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is safe, see above. - $tables = $wpdb->get_col( $tables_sql, 0 ); + $network = 'network' == $table_type; - } elseif ( get_flag_value( $assoc_args, 'all-tables-with-prefix' ) ) { - if ( empty( $tables_sql ) ) { - $tables_sql = $wpdb->prepare( 'SHOW TABLES LIKE %s', esc_like( $wpdb->get_blog_prefix() ) . '%' ); - } else { - $tables_sql .= sprintf( " AND %s LIKE '%s'", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), esc_like( $wpdb->get_blog_prefix() ) . '%' ); - } + if ( 'all-tables' == $table_type ) { + return $wpdb->get_col( 'SHOW TABLES' ); + } - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above. - $tables = $wpdb->get_col( $tables_sql, 0 ); + $prefix = $network ? $wpdb->base_prefix : $wpdb->prefix; + // '_' is a special wildcard for MySQL LIKE queries + // so it needs to be escaped with '\', but then '\' needs to be escaped as well + $sql_prefix = str_replace( '_', '\\_', $prefix ); + $matching_tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $sql_prefix . '%' ) ); - } else { - $scope = get_flag_value( $assoc_args, 'scope', 'all' ); - - // Note: BC change 1.5.0, taking scope into consideration for network also. - if ( get_flag_value( $assoc_args, 'network' ) && is_multisite() ) { - $network_global_scope = in_array( $scope, [ 'all', 'global', 'ms_global' ], true ) ? ( 'all' === $scope ? 'global' : $scope ) : ''; - $wp_tables = array_values( $wpdb->tables( $network_global_scope ) ); - if ( in_array( $scope, [ 'all', 'blog' ], true ) ) { - // Do directly for compat with old WP versions. Note: private, deleted, archived sites are not excluded. - $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = $wpdb->siteid" ); - foreach ( $blog_ids as $blog_id ) { - $wp_tables = array_merge( $wp_tables, array_values( $wpdb->tables( 'blog', true /*prefix*/, $blog_id ) ) ); - } - } - } else { - $wp_tables = array_values( $wpdb->tables( $scope ) ); - } + if ( 'all-tables-with-prefix' == $table_type ) { + return $matching_tables; + } - // The global_terms_enabled() function has been deprecated with WP 6.1+. - // @phpstan-ignore function.deprecated - if ( wp_version_compare( '6.1', '>=' ) || ! global_terms_enabled() ) { // phpcs:ignore WordPress.WP.DeprecatedFunctions.global_terms_enabledFound - // Only include sitecategories when it's actually enabled. - $wp_tables = array_values( array_diff( $wp_tables, [ $wpdb->sitecategories ] ) ); + $filter_sitecategories = function( $matching_tables ) { + global $wpdb; + // Only include sitecategories when it's actually enabled. + if ( ! global_terms_enabled() ) { + $key = array_search( $wpdb->sitecategories, $matching_tables ); + if ( false !== $key ) { + unset( $matching_tables[ $key ] ); + } } + return $matching_tables; + }; - // Note: BC change 1.5.0, tables are sorted (via TABLES view). - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- uses esc_sql_ident() and $wpdb->_escape(). - $tables = $wpdb->get_col( sprintf( "SHOW TABLES WHERE %s IN ('%s')", esc_sql_ident( 'Tables_in_' . $wpdb->dbname ), implode( "', '", $wpdb->_escape( $wp_tables ) ) ) ); + if ( $scope = get_flag_value( $assoc_args, 'scope' ) ) { + $matching_tables = $wpdb->tables( $scope ); + $matching_tables = $filter_sitecategories( $matching_tables ); + return $matching_tables; + } - // Filter tables after the query for improved SQLite compatibility. - // See https://github.com/WordPress/sqlite-database-integration/issues/319. - if ( 'sqlite' === get_db_type() ) { - $tables = array_values( array_intersect( $tables, $wp_tables ) ); + $allowed_tables = array(); + $allowed_table_types = array( 'tables', 'global_tables' ); + if ( $network ) { + $allowed_table_types[] = 'ms_global_tables'; + } + foreach ( $allowed_table_types as $table_type ) { + foreach ( $wpdb->$table_type as $table ) { + $allowed_tables[] = $prefix . $table; } + } + + // Given our matching tables, also allow site-specific tables on the network + foreach ( $matching_tables as $key => $matched_table ) { - if ( get_flag_value( $assoc_args, 'base-tables-only' ) || get_flag_value( $assoc_args, 'views-only' ) ) { - // Apply Views restriction args if needed. - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above. - $views_query_tables = $wpdb->get_col( $tables_sql, 0 ); // @phpstan-ignore variable.undefined - $tables = array_intersect( $tables, $views_query_tables ); + if ( in_array( $matched_table, $allowed_tables ) ) { + continue; } - } - // Filter by `$args`. - if ( $args ) { - $args_tables = []; - foreach ( $args as $arg ) { - if ( false !== strpos( $arg, '*' ) || false !== strpos( $arg, '?' ) ) { - $args_tables = array_merge( - $args_tables, - array_filter( - $tables, - function ( $v ) use ( $arg ) { - return fnmatch( $arg, $v ); - } - ) - ); - } else { - $args_tables[] = $arg; + if ( $network ) { + $valid_table = false; + foreach ( array_merge( $wpdb->tables, $wpdb->old_tables ) as $maybe_site_table ) { + if ( preg_match( "#{$prefix}([\d]+)_{$maybe_site_table}#", $matched_table ) ) { + $valid_table = true; + } + } + if ( $valid_table ) { + continue; } } - $args_tables = array_values( array_unique( $args_tables ) ); - $tables = array_values( array_intersect( $tables, $args_tables ) ); - if ( empty( $tables ) ) { - WP_CLI::error( sprintf( "Couldn't find any tables matching: %s", implode( ' ', $args ) ) ); - } - } - return $tables; -} + unset( $matching_tables[ $key ] ); -/** - * Failsafe use of the WordPress wp_strip_all_tags() function. - * - * Automatically falls back to strip_tags() function if the WP function is not - * available. - * - * @param string $string String to strip the tags from. - * @return string String devoid of tags. - */ -function strip_tags( $string ) { - if ( function_exists( 'wp_strip_all_tags' ) ) { - return \wp_strip_all_tags( $string ); } - $string = (string) preg_replace( - '@<(script|style)[^>]*?>.*?@si', - '', - $string - ); - - // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- Fallback. - $string = \strip_tags( $string ); - - return trim( $string ); + $filter_sitecategories( $matching_tables ); + return array_values( $matching_tables ); } diff --git a/php/utils.php b/php/utils.php index f3a7435e8..be27ede7a 100644 --- a/php/utils.php +++ b/php/utils.php @@ -4,86 +4,32 @@ namespace WP_CLI\Utils; -use ArrayIterator; -use cli; -use cli\progress\Bar; -use cli\Shell; -use Closure; -use Composer\Semver\Comparator; -use Composer\Semver\Semver; -use Exception; -use Iterator; -use Mustache\Engine as Mustache_Engine; -use ReflectionFunction; -use RuntimeException; -use WP_CLI; -use WP_CLI\ExitException; -use WP_CLI\Formatter; -use WP_CLI\Inflector; -use WP_CLI\Iterators\Transform; -use WP_CLI\NoOp; -use WP_CLI\Path; -use WP_CLI\Process; -use WP_CLI\RequestsLibrary; -use WpOrg\Requests\Response; +use \Composer\Semver\Comparator; +use \Composer\Semver\Semver; +use \WP_CLI; +use \WP_CLI\Dispatcher; +use \WP_CLI\Iterators\Transform; -/** - * File stream wrapper prefix for Phar archives. - * - * @var string - */ const PHAR_STREAM_PREFIX = 'phar://'; -/** - * Regular expression pattern to match __FILE__ and __DIR__ constants. - * - * We try to be smart and only replace the constants when they are not within quotes. - * Regular expressions being stateless, this is probably not 100% correct for edge cases. - * - * @see https://regex101.com/r/9hXp5d/11 - * @see https://stackoverflow.com/a/171499/933065 - * - * @var string - */ -const FILE_DIR_PATTERN = '%(?>#.*?$)|(?>//.*?$)|(?>/\*.*?\*/)|(?>\'(?:(?=(\\\\?))\1.)*?\')|(?>"(?:(?=(\\\\?))\2.)*?")|(?\b__FILE__\b)|(?\b__DIR__\b)%ms'; - -/** - * Check if a certain path is within a Phar archive. - * - * If no path is provided, the function checks whether the current WP_CLI instance is - * running from within a Phar archive. - * - * @deprecated 2.13.0 Use Path::inside_phar() instead. - * - * @param string|null $path Optional. Path to check. Defaults to null, which checks WP_CLI_ROOT. - * @return bool Whether path is within a Phar archive. - */ -function inside_phar( $path = null ) { - return Path::inside_phar( $path ); +function inside_phar() { + return 0 === strpos( WP_CLI_ROOT, PHAR_STREAM_PREFIX ); } -/** - * Extract a file from a Phar archive. - * - * Files that need to be read by external programs have to be extracted from the Phar archive. - * If the file is not within a Phar archive, the function returns the path unchanged. - * - * @param string $path Path to the file to extract. - * @return string Path to the extracted file. - */ +// Files that need to be read by external programs have to be extracted from the Phar archive. function extract_from_phar( $path ) { - if ( ! Path::inside_phar( $path ) ) { + if ( ! inside_phar() ) { return $path; } - $fname = Path::basename( $path ); + $fname = basename( $path ); - $tmp_path = get_temp_dir() . uniqid( 'wp-cli-extract-from-phar-', true ) . "-$fname"; + $tmp_path = get_temp_dir() . "wp-cli-$fname"; copy( $path, $tmp_path ); register_shutdown_function( - function () use ( $tmp_path ) { + function() use ( $tmp_path ) { if ( file_exists( $tmp_path ) ) { unlink( $tmp_path ); } @@ -93,13 +39,8 @@ function () use ( $tmp_path ) { return $tmp_path; } -/** - * Load dependencies. - * - * @return void|never - */ function load_dependencies() { - if ( Path::inside_phar() ) { + if ( inside_phar() ) { if ( file_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) { require WP_CLI_ROOT . '/vendor/autoload.php'; } elseif ( file_exists( dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php' ) ) { @@ -124,22 +65,14 @@ function load_dependencies() { } } -/** - * Return vendor paths. - * - * @return array List of paths. - */ function get_vendor_paths() { - $vendor_paths = [ - WP_CLI_ROOT . '/../../../vendor', // Part of a larger project / installed via Composer (preferred). - WP_CLI_ROOT . '/vendor', // Top-level project / installed as Git clone. - ]; + $vendor_paths = array( + WP_CLI_ROOT . '/../../../vendor', // part of a larger project / installed via Composer (preferred) + WP_CLI_ROOT . '/vendor', // top-level project / installed as Git clone + ); $maybe_composer_json = WP_CLI_ROOT . '/../../../composer.json'; if ( file_exists( $maybe_composer_json ) && is_readable( $maybe_composer_json ) ) { - /** - * @var object{config: object{'vendor-dir': string}} $composer - */ - $composer = json_decode( (string) file_get_contents( $maybe_composer_json ), false ); + $composer = json_decode( file_get_contents( $maybe_composer_json ) ); if ( ! empty( $composer->config ) && ! empty( $composer->config->{'vendor-dir'} ) ) { array_unshift( $vendor_paths, WP_CLI_ROOT . '/../../../' . $composer->config->{'vendor-dir'} ); } @@ -147,25 +80,11 @@ function get_vendor_paths() { return $vendor_paths; } -/** - * Load a file. - * - * Using require() directly inside a class grants access - * to private methods to the loaded code, hence this wrapper helper. - * - * @param string $path - * @return void - */ +// Using require() directly inside a class grants access to private methods to the loaded code function load_file( $path ) { require_once $path; } -/** - * Load a command. - * - * @param string $name - * @return void - */ function load_command( $name ) { $path = WP_CLI_ROOT . "/php/commands/$name.php"; @@ -189,23 +108,20 @@ function load_command( $name ) { * var_dump($val); * } * - * @param array|Iterator $it Either a plain array or another iterator. - * @param callable ...$fns The function to apply to an element. - * @return Iterator An iterator that applies the given callback(s). + * @param array|object Either a plain array or another iterator + * @param callback The function to apply to an element + * @return object An iterator that applies the given callback(s) */ -function iterator_map( $it, ...$fns ) { +function iterator_map( $it, $fn ) { if ( is_array( $it ) ) { - $it = new ArrayIterator( $it ); + $it = new \ArrayIterator( $it ); } if ( ! method_exists( $it, 'add_transform' ) ) { $it = new Transform( $it ); } - foreach ( $fns as $fn ) { - /** - * @var Transform $it - */ + foreach ( array_slice( func_get_args(), 1 ) as $fn ) { $it->add_transform( $fn ); } @@ -213,70 +129,18 @@ function iterator_map( $it, ...$fns ) { } /** - * Check if a path is within open_basedir restrictions. - * - * This function compares paths using string operations to avoid triggering warnings - * when checking paths that may be outside open_basedir restrictions. - * - * @param string $path The path to check (should be absolute). - * @return bool True if the path is accessible (no open_basedir or within allowed paths), false otherwise. - */ -function is_path_within_open_basedir( $path ) { - $open_basedir = ini_get( 'open_basedir' ); - if ( empty( $open_basedir ) ) { - return true; - } - - // Normalize the path to check and remove trailing slashes. - $path = Path::normalize( $path ); - $path = rtrim( $path, '/\\' ); - - $allowed_paths = explode( PATH_SEPARATOR, $open_basedir ); - foreach ( $allowed_paths as $allowed ) { - if ( empty( $allowed ) ) { - continue; - } - // Normalize the allowed path using realpath (allowed paths should be accessible). - $allowed = rtrim( $allowed, '/\\' ); - $real_allowed = realpath( $allowed ); - if ( false !== $real_allowed ) { - $allowed = $real_allowed; - } - $allowed = Path::normalize( $allowed ); - $allowed = rtrim( $allowed, '/\\' ); - // Check if path starts with allowed directory. - // On Windows, use case-insensitive comparison as filesystem paths are case-insensitive. - $is_windows = is_windows(); - if ( $is_windows ) { - if ( 0 === stripos( $path . '/', $allowed . '/' ) ) { - return true; - } - } elseif ( 0 === strpos( $path . '/', $allowed . '/' ) ) { - return true; - } - } - return false; -} - -/** - * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true. - * - * @param string|array $files The files (or file) to search for. - * @param string|null $dir The directory to start searching from; defaults to CWD. - * @param callable $stop_check Function which is passed the current dir each time a directory level is traversed. - * @return null|string Null if the file was not found. + * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true + * @param string|array The files (or file) to search for + * @param string|null The directory to start searching from; defaults to CWD + * @param callable Function which is passed the current dir each time a directory level is traversed + * @return null|string Null if the file was not found */ function find_file_upward( $files, $dir = null, $stop_check = null ) { $files = (array) $files; if ( is_null( $dir ) ) { $dir = getcwd(); } - // Normalize the directory path using string operations to avoid filesystem access - // that could trigger open_basedir warnings - if ( false !== $dir ) { - $dir = Path::normalize( $dir ); - } - while ( $dir && is_path_within_open_basedir( $dir ) && is_readable( $dir ) ) { + while ( is_readable( $dir ) ) { // Stop walking up when the supplied callable returns true being passed the $dir if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { return null; @@ -298,99 +162,47 @@ function find_file_upward( $files, $dir = null, $stop_check = null ) { return null; } -/** - * Determine whether a path is absolute. - * - * @deprecated 2.13.0 Use Path::is_absolute() instead. - * - * @param string $path - * @return bool - */ function is_path_absolute( $path ) { - return Path::is_absolute( $path ); -} - -/** - * Expand tilde (~) in path to home directory. - * - * Expands paths that start with ~ to the current user's home directory. - * Only handles the current user's home directory (not ~username patterns). - * - * @deprecated 2.13.0 Use Path::expand_tilde() instead. - * - * @param string $path Path that may contain a tilde. - * @return string Path with tilde expanded to home directory, or unchanged if tilde not at start or followed by username. - */ -function expand_tilde_path( $path ) { - return Path::expand_tilde( $path ); -} - -/** - * Escape a shell argument while preserving tilde expansion. - * - * This function is useful when passing paths to remote shells (e.g., via SSH) - * where tilde expansion should occur on the remote system. Unlike escapeshellarg(), - * this function allows tilde at the start of a path to be expanded by the remote shell. - * - * For paths starting with ~/: returns ~/ followed by the escaped remainder. - * For all other paths: returns the fully escaped path using escapeshellarg(). - * - * @param string $arg The argument to escape. - * @return string The escaped argument. - */ -function escapeshellarg_preserve_tilde( $arg ) { - // Check if argument starts with ~/ - if ( substr( $arg, 0, 2 ) === '~/' ) { - // Extract everything after ~/ - $remainder = substr( $arg, 2 ); - // Return ~/ followed by the escaped remainder - return '~/' . escapeshellarg( $remainder ); + // Windows + if ( isset( $path[1] ) && ':' === $path[1] ) { + return true; } - // For all other cases, use standard escapeshellarg - return escapeshellarg( $arg ); + return '/' === $path[0]; } /** * Composes positional arguments into a command string. * - * @param array $args Positional arguments to compose. + * @param array * @return string */ function args_to_str( $args ) { - return ' ' . implode( ' ', array_map( 'escapeshellarg', array_map( 'strval', $args ) ) ); + return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); } /** * Composes associative arguments into a command string. * - * @param array $assoc_args Associative arguments to compose. - * @param array $sensitive_args Optional. Array of argument keys that should be masked. + * @param array * @return string */ -function assoc_args_to_str( $assoc_args, $sensitive_args = [] ) { +function assoc_args_to_str( $assoc_args ) { $str = ''; foreach ( $assoc_args as $key => $value ) { if ( true === $value ) { $str .= " --$key"; } elseif ( is_array( $value ) ) { - foreach ( $value as $v ) { + foreach ( $value as $_ => $v ) { $str .= assoc_args_to_str( - [ + array( $key => $v, - ], - $sensitive_args + ) ); } - } elseif ( in_array( $key, $sensitive_args, true ) ) { - // Mask the value if this is a sensitive argument - $str .= " --$key=" . escapeshellarg( '[REDACTED]' ); } else { - /** - * @var string|int $value - */ - $str .= " --$key=" . escapeshellarg( (string) $value ); + $str .= " --$key=" . escapeshellarg( $value ); } } @@ -400,37 +212,29 @@ function assoc_args_to_str( $assoc_args, $sensitive_args = [] ) { /** * Given a template string and an arbitrary number of arguments, * returns the final command, with the parameters escaped. - * - * @param string $cmd - * @param string ...$args */ -function esc_cmd( $cmd, ...$args ) { +function esc_cmd( $cmd ) { if ( func_num_args() < 2 ) { trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); } + $args = func_get_args(); + + $cmd = array_shift( $args ); + return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) ); } -/** - * Gets path to WordPress configuration. - * - * @return string - */ function locate_wp_config() { static $path; if ( null === $path ) { $path = false; - $config_path = (string) getenv( 'WP_CONFIG_PATH' ); - - if ( $config_path && file_exists( $config_path ) ) { - $path = $config_path; - } elseif ( file_exists( ABSPATH . 'wp-config.php' ) ) { + if ( file_exists( ABSPATH . 'wp-config.php' ) ) { $path = ABSPATH . 'wp-config.php'; - } elseif ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) { - $path = dirname( ABSPATH ) . '/wp-config.php'; + } elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) ) { + $path = ABSPATH . '../wp-config.php'; } if ( $path ) { @@ -441,20 +245,9 @@ function locate_wp_config() { return $path; } -/** - * Compare a WordPress version. - * - * @param string $since - * @param string $operator - * @return bool - */ function wp_version_compare( $since, $operator ) { - /** - * @var string $wp_version - */ - $wp_version = $GLOBALS['wp_version']; - $wp_version = str_replace( '-src', '', $wp_version ); - $since = str_replace( '-src', '', $since ); + $wp_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); + $since = str_replace( '-src', '', $since ); return version_compare( $wp_version, $since, $operator ); } @@ -498,16 +291,14 @@ function wp_version_compare( $since, $operator ) { * @access public * @category Output * - * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count'. - * @param array $items An array of items to output. - * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. + * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count' + * @param array $items An array of items to output. + * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. + * @return null */ function format_items( $format, $items, $fields ) { - $assoc_args = [ - 'format' => $format, - 'fields' => $fields, - ]; - $formatter = new Formatter( $assoc_args ); + $assoc_args = compact( 'format', 'fields' ); + $formatter = new \WP_CLI\Formatter( $assoc_args ); $formatter->display_items( $items ); } @@ -516,53 +307,38 @@ function format_items( $format, $items, $fields ) { * * @access public * - * @param resource $fd File descriptor. - * @param array|iterable $rows Array of rows to output. - * @param array $headers List of CSV columns (optional). + * @param resource $fd File descriptor + * @param array $rows Array of rows to output + * @param array $headers List of CSV columns (optional) */ -function write_csv( $fd, $rows, $headers = [] ) { +function write_csv( $fd, $rows, $headers = array() ) { if ( ! empty( $headers ) ) { - $headers = array_map( __NAMESPACE__ . '\escape_csv_value', $headers ); - fputcsv( $fd, $headers, ',', '"', '\\' ); + fputcsv( $fd, $headers ); } - /** - * @var string[] $row - */ foreach ( $rows as $row ) { if ( ! empty( $headers ) ) { $row = pick_fields( $row, $headers ); } - /** - * @var string[] $row - * @var callable $callback - */ - - $callback = __NAMESPACE__ . '\escape_csv_value'; - $row = array_map( $callback, $row ); - fputcsv( $fd, array_values( $row ), ',', '"', '\\' ); + fputcsv( $fd, array_values( $row ) ); } } /** * Pick fields from an associative array or object. * - * @param array|object $item Associative array or object to pick fields from. - * @param array $fields List of fields to pick. - * @return array + * @param array|object Associative array or object to pick fields from + * @param array List of fields to pick + * @return array */ function pick_fields( $item, $fields ) { - $values = []; + $item = (object) $item; - if ( is_object( $item ) ) { - foreach ( $fields as $field ) { - $values[ $field ] = isset( $item->$field ) ? $item->$field : null; - } - } else { - foreach ( $fields as $field ) { - $values[ $field ] = isset( $item[ $field ] ) ? $item[ $field ] : null; - } + $values = array(); + + foreach ( $fields as $field ) { + $values[ $field ] = isset( $item->$field ) ? $item->$field : null; } return $values; @@ -574,23 +350,21 @@ function pick_fields( $item, $fields ) { * @access public * @category Input * - * @param string $input Some form of text to edit (e.g. post content). - * @param string $title Title to display in the editor. - * @param string $ext Extension to use with the temp file. - * @return string|bool Edited text, if file is saved from editor; false, if no change to file. + * @param string $content Some form of text to edit (e.g. post content) + * @return string|bool Edited text, if file is saved from editor; false, if no change to file. */ -function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) { +function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { check_proc_available( 'launch_editor_for_input' ); $tmpdir = get_temp_dir(); do { - $tmpfile = Path::basename( $title ); - $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); - $tmpfile .= '-' . substr( md5( (string) mt_rand() ), 0, 6 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- no crypto and WP not loaded. - $tmpfile = $tmpdir . $tmpfile . '.' . $ext; - $fp = fopen( $tmpfile, 'xb' ); + $tmpfile = basename( $filename ); + $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); + $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); + $tmpfile = $tmpdir . $tmpfile . '.tmp'; + $fp = fopen( $tmpfile, 'xb' ); if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { $tmpfile = ''; continue; @@ -600,11 +374,11 @@ function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) { } } while ( ! $tmpfile ); - // @phpstan-ignore booleanNot.alwaysFalse if ( ! $tmpfile ) { - WP_CLI::error( 'Error creating temporary file.' ); + \WP_CLI::error( 'Error creating temporary file.' ); } + $output = ''; file_put_contents( $tmpfile, $input ); $editor = getenv( 'EDITOR' ); @@ -612,13 +386,11 @@ function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) { $editor = is_windows() ? 'notepad' : 'vi'; } - $descriptorspec = [ STDIN, STDOUT, STDERR ]; - $process = proc_open_compat( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); - if ( $process ) { - $r = proc_close( $process ); - if ( $r ) { - exit( $r ); - } + $descriptorspec = array( STDIN, STDOUT, STDERR ); + $process = proc_open_compat( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); + $r = proc_close( $process ); + if ( $r ) { + exit( $r ); } $output = file_get_contents( $tmpfile ); @@ -633,27 +405,18 @@ function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) { } /** - * @param string $raw_host MySQL host string, as defined in wp-config.php. - * - * @return array + * @param string MySQL host string, as defined in wp-config.php + * @return array */ function mysql_host_to_cli_args( $raw_host ) { - $assoc_args = []; - - /** - * If the host string begins with 'p:' for a persistent db connection, - * replace 'p:' with nothing. - */ - if ( substr( $raw_host, 0, 2 ) === 'p:' ) { - $raw_host = substr_replace( $raw_host, '', 0, 2 ); - } + $assoc_args = array(); - $host_parts = explode( ':', $raw_host ); - if ( count( $host_parts ) === 2 ) { + $host_parts = explode( ':', $raw_host ); + if ( count( $host_parts ) == 2 ) { list( $assoc_args['host'], $extra ) = $host_parts; - $extra = trim( $extra ); + $extra = trim( $extra ); if ( is_numeric( $extra ) ) { - $assoc_args['port'] = (int) $extra; + $assoc_args['port'] = (int) $extra; $assoc_args['protocol'] = 'tcp'; } elseif ( '' !== $extra ) { $assoc_args['socket'] = $extra; @@ -665,123 +428,61 @@ function mysql_host_to_cli_args( $raw_host ) { return $assoc_args; } -/** - * Run a MySQL command and optionally return the output. - * - * @since v2.5.0 Deprecated $descriptors argument. - * - * @param string $cmd Command to run. - * @param array $assoc_args Associative array of arguments to use. - * @param mixed $_ Deprecated. Former $descriptors argument. - * @param bool $send_to_shell Optional. Whether to send STDOUT and STDERR - * immediately to the shell. Defaults to true. - * @param bool $interactive Optional. Whether MySQL is meant to be - * executed as an interactive process. Defaults - * to false. - * - * @return array { - * Associative array containing STDOUT and STDERR output. - * - * @type string $stdout Output that was sent to STDOUT. - * @type string $stderr Output that was sent to STDERR. - * @type int $exit_code Exit code of the process. - * } - */ -function run_mysql_command( $cmd, $assoc_args, $_ = null, $send_to_shell = true, $interactive = false ) { +function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { check_proc_available( 'run_mysql_command' ); - /** - * @var array $descriptors - */ - $descriptors = ( $interactive || $send_to_shell ) ? - [ - 0 => STDIN, - 1 => STDOUT, - 2 => STDERR, - ] : - [ - 0 => STDIN, - 1 => [ 'pipe', 'w' ], - 2 => [ 'pipe', 'w' ], - ]; - - $stdout = ''; - $stderr = ''; - - /** - * @var array $pipes - */ - $pipes = []; + if ( ! $descriptors ) { + $descriptors = array( STDIN, STDOUT, STDERR ); + } if ( isset( $assoc_args['host'] ) ) { - // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_host_to_cli_args -- Misidentified as PHP native MySQL function. + //@codingStandardsIgnoreStart $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); + //@codingStandardsIgnoreEnd } - if ( isset( $assoc_args['pass'] ) ) { - $old_password = getenv( 'MYSQL_PWD' ); - putenv( 'MYSQL_PWD=' . $assoc_args['pass'] ); - unset( $assoc_args['pass'] ); - } + $pass = $assoc_args['pass']; + unset( $assoc_args['pass'] ); - $final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args ); - - WP_CLI::debug( 'Final MySQL command: ' . $final_cmd, 'db' ); - $process = proc_open_compat( $final_cmd, $descriptors, $pipes ); + $old_pass = getenv( 'MYSQL_PWD' ); + putenv( 'MYSQL_PWD=' . $pass ); - if ( isset( $old_password ) ) { - putenv( 'MYSQL_PWD=' . $old_password ); - } + $final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args ); - if ( ! $process ) { - WP_CLI::debug( 'Failed to create a valid process using proc_open_compat()', 'db' ); + $proc = proc_open_compat( $final_cmd, $descriptors, $pipes ); + if ( ! $proc ) { exit( 1 ); } - if ( is_resource( $process ) && ! $send_to_shell && ! $interactive ) { - $stdout = stream_get_contents( $pipes[1] ); - $stderr = stream_get_contents( $pipes[2] ); - - fclose( $pipes[1] ); - fclose( $pipes[2] ); - } + $r = proc_close( $proc ); - $exit_code = proc_close( $process ); + putenv( 'MYSQL_PWD=' . $old_pass ); - if ( $exit_code && ( $send_to_shell || $interactive ) ) { - exit( $exit_code ); + if ( $r ) { + exit( $r ); } - - return [ - $stdout, - $stderr, - $exit_code, - ]; } /** * Render PHP or other types of files using Mustache templates. * * IMPORTANT: Automatic HTML escaping is disabled! - * - * @param string $template_name - * @param array $data */ -function mustache_render( $template_name, $data = [] ) { +function mustache_render( $template_name, $data = array() ) { if ( ! file_exists( $template_name ) ) { $template_name = WP_CLI_ROOT . "/templates/$template_name"; } - $template = (string) file_get_contents( $template_name ); + $template = file_get_contents( $template_name ); - $mustache = new Mustache_Engine( - [ + $m = new \Mustache_Engine( + array( 'escape' => function ( $val ) { return $val; }, - ] + ) ); - return $mustache->render( $template, $data ); + return $m->render( $template, $data ); } /** @@ -811,49 +512,21 @@ function mustache_render( $template_name, $data = [] ) { * @param string $message Text to display before the progress bar. * @param integer $count Total number of ticks to be performed. * @param int $interval Optional. The interval in milliseconds between updates. Default 100. - * @return \cli\progress\Bar|\WP_CLI\NoOp + * @return cli\progress\Bar|WP_CLI\NoOp */ function make_progress_bar( $message, $count, $interval = 100 ) { - if ( Shell::isPiped() ) { - return new NoOp(); + if ( \cli\Shell::isPiped() ) { + return new \WP_CLI\NoOp; } - return new Bar( $message, $count, $interval ); + return new \cli\progress\Bar( $message, $count, $interval ); } -/** - * Helper function to use wp_parse_url when available or fall back to PHP's - * parse_url if not. - * - * Additionally, this adds 'http://' to the URL if no scheme was found. - * - * @param string $url The URL to parse. - * @param int $component Optional. The specific component to retrieve. - * Use one of the PHP predefined constants to - * specify which one. Defaults to -1 (= return - * all parts as an array). - * @param bool $auto_add_scheme Optional. Automatically add an http:// scheme if - * none was found. Defaults to true. - * @return mixed False on parse failure; Array of URL components on success; - * When a specific component has been requested: null if the - * component doesn't exist in the given URL; a string or - in the - * case of PHP_URL_PORT - integer when it does. See parse_url()'s - * return values. - * - * @phpstan-return ($component is non-negative-int ? string|null|int|false : array{scheme?: string, host?: string, port?: int, user?: string, pass?: string, query?: string, path?: string, fragment?: string}) - */ -function parse_url( $url, $component = - 1, $auto_add_scheme = true ) { - if ( function_exists( 'wp_parse_url' ) ) { - $url_parts = wp_parse_url( $url, $component ); - } else { - // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Fallback. - $url_parts = \parse_url( $url, $component ); - } +function parse_url( $url ) { + $url_parts = \parse_url( $url ); - // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Own version based on WP one. - if ( $auto_add_scheme && ! parse_url( $url, PHP_URL_SCHEME, false ) ) { - // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Own version based on WP one. - $url_parts = parse_url( 'http://' . $url, $component, false ); + if ( ! isset( $url_parts['scheme'] ) ) { + $url_parts = parse_url( 'http://' . $url ); } return $url_parts; @@ -865,24 +538,25 @@ function parse_url( $url, $component = - 1, $auto_add_scheme = true ) { * @return bool */ function is_windows() { - $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); - return false !== $test_is_windows ? (bool) $test_is_windows : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN'; + return false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN'; } /** * Replace magic constants in some PHP source code. * - * Replaces the __FILE__ and __DIR__ magic constants with the values they are - * supposed to represent at runtime. - * - * @deprecated 2.13.0 Use Path::replace_path_consts() instead. - * * @param string $source The PHP code to manipulate. - * @param string $path The path to use instead of the magic constants. - * @return string Adapted PHP code. + * @param string $path The path to use instead of the magic constants */ function replace_path_consts( $source, $path ) { - return Path::replace_path_consts( $source, $path ); + $replacements = array( + '__FILE__' => "'$path'", + '__DIR__' => "'" . dirname( $path ) . "'", + ); + + $old = array_keys( $replacements ); + $new = array_values( $replacements ); + + return str_replace( $old, $new, $source ); } /** @@ -901,256 +575,115 @@ function replace_path_consts( $source, $path ) { * * @access public * - * @param string $method HTTP method (GET, POST, DELETE, etc.). - * @param string $url URL to make the HTTP request to. - * @param array|null $data Data to send either as a query string for GET/HEAD requests, - * or in the body for POST requests. - * @param array $headers Add specific headers to the request. - * @param array $options { - * Optional. An associative array of additional request options. - * - * @type bool $halt_on_error Whether or not command execution should be halted on error. Default: true - * @type bool|string $verify A boolean to use enable/disable SSL verification - * or string absolute path to CA cert to use. - * Defaults to detected CA cert bundled with the Requests library. - * @type bool $insecure Whether to retry automatically without certificate validation. - * @type int $max_retries Maximum number of retries of failed requests. Default 3. - * } - * @return \Requests_Response|Response - * @throws RuntimeException If the request failed. - * @throws ExitException If the request failed and $halt_on_error is true. - * - * @phpstan-param array{halt_on_error?: bool, verify?: bool|string, insecure?: bool} $options + * @param string $method HTTP method (GET, POST, DELETE, etc.) + * @param string $url URL to make the HTTP request to. + * @param array $headers Add specific headers to the request. + * @param array $options + * @return object */ -function http_request( $method, $url, $data = null, $headers = [], $options = [] ) { - $insecure = isset( $options['insecure'] ) && (bool) $options['insecure']; - $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; - $max_retries = isset( $options['max_retries'] ) ? (int) $options['max_retries'] : 3; - unset( $options['halt_on_error'] ); - - if ( ! isset( $options['verify'] ) ) { - // 'curl.cainfo' enforces the CA file to use, otherwise fallback to system-wide defaults then use the embedded CA file. - $options['verify'] = ! empty( ini_get( 'curl.cainfo' ) ) ? ini_get( 'curl.cainfo' ) : true; - } - - /** - * @var array{halt_on_error?: bool, verify: bool|string, insecure?: bool} $options - */ - $options = WP_CLI::do_hook( 'http_request_options', $options, $method, $url, $data, $headers ); - - RequestsLibrary::register_autoloader(); - - /** - * @var callable $request_method - */ - $request_method = [ RequestsLibrary::get_class_name(), 'request' ]; +function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { - $attempt = 0; - $last_exception = null; - $retry_after_delay = 1; // Start with 1 second delay. - - while ( $attempt < $max_retries ) { - ++$attempt; - try { - try { - return $request_method( $url, $headers, $data, $method, $options ); - } catch ( \Requests_Exception | \WpOrg\Requests\Exception $exception ) { - $curl_handle = $exception->getData(); - // Get curl error code safely - only if curl is available and handle is valid. - $curl_errno = null; - if ( function_exists( 'curl_errno' ) && ( is_resource( $curl_handle ) || ( is_object( $curl_handle ) && $curl_handle instanceof \CurlHandle ) ) ) { - // @phpstan-ignore argument.type - $curl_errno = curl_errno( $curl_handle ); - } - // CURLE_SSL_CACERT = 60 - $is_ssl_cacert_error = null !== $curl_errno && 60 === $curl_errno; - - if ( - true !== $options['verify'] - || 'curlerror' !== $exception->getType() - || ! $is_ssl_cacert_error - ) { - throw $exception; - } - - $options['verify'] = get_default_cacert( $halt_on_error ); - - return $request_method( $url, $headers, $data, $method, $options ); + $cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; + $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; + if ( inside_phar() ) { + // cURL can't read Phar archives + $options['verify'] = extract_from_phar( + WP_CLI_VENDOR_DIR . $cert_path + ); + } else { + foreach ( get_vendor_paths() as $vendor_path ) { + if ( file_exists( $vendor_path . $cert_path ) ) { + $options['verify'] = $vendor_path . $cert_path; + break; } - } catch ( \Requests_Exception | \WpOrg\Requests\Exception $exception ) { - $curl_handle = $exception->getData(); - // Get curl error code safely - only if curl is available and handle is valid. - $curl_errno = null; - if ( function_exists( 'curl_errno' ) && ( is_resource( $curl_handle ) || ( is_object( $curl_handle ) && $curl_handle instanceof \CurlHandle ) ) ) { - // @phpstan-ignore argument.type - $curl_errno = curl_errno( $curl_handle ); + } + if ( empty( $options['verify'] ) ) { + $error_msg = 'Cannot find SSL certificate.'; + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); } - // CURLE_SSL_CONNECT_ERROR = 35, CURLE_SSL_CERTPROBLEM = 58, CURLE_SSL_CACERT_BADFILE = 77 - $is_ssl_error = null !== $curl_errno && in_array( $curl_errno, [ 35, 58, 77 ], true ); - - // CURLE_COULDNT_RESOLVE_HOST = 6, CURLE_COULDNT_CONNECT = 7, CURLE_PARTIAL_FILE = 18 - // CURLE_OPERATION_TIMEDOUT = 28, CURLE_GOT_NOTHING = 52, CURLE_SEND_ERROR = 55, CURLE_RECV_ERROR = 56 - $is_transient_error = null !== $curl_errno && in_array( $curl_errno, [ 6, 7, 18, 28, 52, 55, 56 ], true ); - - if ( - ! $insecure - || - 'curlerror' !== $exception->getType() - || - ! $is_ssl_error - ) { - // Check if this is a transient error that should be retried. - if ( ! $is_transient_error || $attempt >= $max_retries ) { - $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $exception->getMessage() ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - throw new RuntimeException( $error_msg, 0, $exception ); - } + throw new \RuntimeException( $error_msg ); + } + } - // Store exception and retry. - $last_exception = $exception; - WP_CLI::debug( sprintf( 'Retrying HTTP request to %s (retry %d/%d) after transient error: %s', $url, $attempt, $max_retries, $exception->getMessage() ), 'http' ); - sleep( $retry_after_delay ); - $retry_after_delay = min( $retry_after_delay * 2, 10 ); // Exponential backoff, max 10 seconds. - continue; + try { + return \Requests::request( $url, $headers, $data, $method, $options ); + } catch ( \Requests_Exception $ex ) { + // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. + if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ), true ) ) { + $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); } - - $warning = sprintf( - "Re-trying without verify after failing to get verified url '%s' %s.", - $url, - $exception->getMessage() - ); - WP_CLI::warning( $warning ); - - // Disable certificate validation for the next try. - $options['verify'] = false; - - try { - return $request_method( $url, $headers, $data, $method, $options ); - } catch ( \Requests_Exception | \WpOrg\Requests\Exception $retry_exception ) { - // Check if this is a transient error that should be retried. - $retry_curl_handle = $retry_exception->getData(); - $retry_curl_errno = null; - if ( function_exists( 'curl_errno' ) && ( is_resource( $retry_curl_handle ) || ( is_object( $retry_curl_handle ) && $retry_curl_handle instanceof \CurlHandle ) ) ) { - // @phpstan-ignore argument.type - $retry_curl_errno = curl_errno( $retry_curl_handle ); - } - $is_retry_transient = null !== $retry_curl_errno && in_array( $retry_curl_errno, [ 6, 7, 18, 28, 52, 55, 56 ], true ); - - if ( $is_retry_transient && $attempt < $max_retries ) { - // Transient error, let the retry loop handle it. - $last_exception = $retry_exception; - WP_CLI::debug( sprintf( 'Retrying HTTP request to %s (retry %d/%d) after transient error: %s', $url, $attempt, $max_retries, $retry_exception->getMessage() ), 'http' ); - sleep( $retry_after_delay ); - $retry_after_delay = min( $retry_after_delay * 2, 10 ); // Exponential backoff, max 10 seconds. - continue; - } - - $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $retry_exception->getMessage() ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - throw new RuntimeException( $error_msg, 0, $retry_exception ); + throw new \RuntimeException( $error_msg, null, $ex ); + } + // Handle SSL certificate issues gracefully + \WP_CLI::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) ); + $options['verify'] = false; + try { + return \Requests::request( $url, $headers, $data, $method, $options ); + } catch ( \Requests_Exception $ex ) { + $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); } + throw new \RuntimeException( $error_msg, null, $ex ); } } - - // All retries exhausted, throw the last exception. - $error_msg = sprintf( "Failed to get url '%s' after %d attempts.", $url, $max_retries ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - throw new RuntimeException( $error_msg, 0, $last_exception ); } /** - * Gets the full path to the default CA cert. + * Increments a version string using the "x.y.z-pre" format * - * @param bool $halt_on_error Whether or not command execution should be halted on error. Default: false - * @return string Absolute path to the default CA cert. - * @throws RuntimeException If unable to locate the cert. - * @throws ExitException If unable to locate the cert and $halt_on_error is true. - */ -function get_default_cacert( $halt_on_error = false ) { - $cert_path = RequestsLibrary::get_bundled_certificate_path(); - $error_msg = 'Cannot find SSL certificate.'; - - if ( Path::inside_phar( $cert_path ) ) { - // cURL can't read Phar archives. - return extract_from_phar( $cert_path ); - } - - if ( file_exists( $cert_path ) ) { - return $cert_path; - } - - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); - } - - throw new RuntimeException( $error_msg ); -} - -/** - * Increments a version string using the "x.y.z-pre" format. + * Can increment the major, minor or patch number by one + * If $new_version == "same" the version string is not changed + * If $new_version is not a known keyword, it will be used as the new version string directly * - * Can increment the major, minor or patch number by one. - * If $new_version == "same" the version string is not changed. - * If $new_version is not a known keyword, it will be used as the new version string directly. - * - * @param string $current_version - * @param string $new_version + * @param string $current_version + * @param string $new_version * @return string */ function increment_version( $current_version, $new_version ) { - // split version assuming the format is x.y.z-pre. - $_current_version = explode( '-', $current_version, 2 ); - $_current_version[0] = explode( '.', $_current_version[0] ); - - $_current_version = array_slice( $_current_version, 0, 2 ); + // split version assuming the format is x.y.z-pre + $current_version = explode( '-', $current_version, 2 ); + $current_version[0] = explode( '.', $current_version[0] ); - /** - * @var array{0: list, 1?: string|list|null} $_current_version - */ - // @phpstan-ignore varTag.type switch ( $new_version ) { case 'same': - // do nothing. + // do nothing break; case 'patch': - $_current_version[0][2] = (int) $_current_version[0][2] + 1; + $current_version[0][2]++; - $_current_version = [ $_current_version[0] ]; // Drop possible pre-release info. + $current_version = array( $current_version[0] ); // drop possible pre-release info break; case 'minor': - $_current_version[0][1] = (int) $_current_version[0][1] + 1; - $_current_version[0][2] = 0; + $current_version[0][1]++; + $current_version[0][2] = 0; - $_current_version = [ $_current_version[0] ]; // Drop possible pre-release info. + $current_version = array( $current_version[0] ); // drop possible pre-release info break; case 'major': - $_current_version[0][0] = (int) $_current_version[0][0] + 1; - $_current_version[0][1] = 0; - $_current_version[0][2] = 0; + $current_version[0][0]++; + $current_version[0][1] = 0; + $current_version[0][2] = 0; - $_current_version = [ $_current_version[0] ]; // Drop possible pre-release info. + $current_version = array( $current_version[0] ); // drop possible pre-release info break; - default: // not a keyword. - $_current_version = [ [ $new_version ] ]; + default: // not a keyword + $current_version = array( array( $new_version ) ); break; } - // Reconstruct version string. - $_current_version[0] = implode( '.', $_current_version[0] ); - // @phpstan-ignore argument.type - $_current_version = implode( '-', $_current_version ); + // reconstruct version string + $current_version[0] = implode( '.', $current_version[0] ); + $current_version = implode( '-', $current_version ); - return $_current_version; + return $current_version; } /** @@ -1160,7 +693,7 @@ function increment_version( $current_version, $new_version ) { * * @param string $new_version * @param string $original_version - * @return string 'major', 'minor', 'patch' + * @return string $name 'major', 'minor', 'patch' */ function get_named_sem_ver( $new_version, $original_version ) { @@ -1169,22 +702,21 @@ function get_named_sem_ver( $new_version, $original_version ) { } $parts = explode( '-', $original_version ); - $bits = explode( '.', $parts[0] ); + $bits = explode( '.', $parts[0] ); $major = $bits[0]; if ( isset( $bits[1] ) ) { $minor = $bits[1]; } + if ( isset( $bits[2] ) ) { + $patch = $bits[2]; + } - try { - if ( isset( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { - return 'patch'; - } + if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { + return 'patch'; + } - if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { - return 'minor'; - } - } catch ( \UnexpectedValueException $e ) { - return ''; + if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { + return 'minor'; } return 'major'; @@ -1200,10 +732,10 @@ function get_named_sem_ver( $new_version, $original_version ) { * @access public * @category Input * - * @param array $assoc_args Arguments array. - * @param string|int $flag Flag to get the value. - * @param string|bool|int|null $default Default value for the flag. Default: NULL. - * @return string|bool|int|null + * @param array $assoc_args Arguments array. + * @param string $flag Flag to get the value. + * @param mixed $default Default value for the flag. Default: NULL + * @return mixed */ function get_flag_value( $assoc_args, $flag, $default = null ) { return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; @@ -1212,22 +744,24 @@ function get_flag_value( $assoc_args, $flag, $default = null ) { /** * Get the home directory. * - * @deprecated 2.13.0 Use Path::get_home_dir() instead. - * * @access public * @category System * * @return string */ function get_home_dir() { - return Path::get_home_dir(); + $home = getenv( 'HOME' ); + if ( ! $home ) { + // In Windows $HOME may not be defined + $home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); + } + + return rtrim( $home, '/\\' ); } /** * Appends a trailing slash. * - * @deprecated 2.13.0 Use Path::trailingslashit() instead. - * * @access public * @category System * @@ -1235,55 +769,7 @@ function get_home_dir() { * @return string String with trailing slash added. */ function trailingslashit( $string ) { - return Path::trailingslashit( $string ); -} - -/** - * Check if a path is a PHP stream URL. - * - * @deprecated 2.13.0 Use Path::is_stream() instead. - * - * @access public - * @category System - * - * @param string $path The resource path or URL. - * @return bool True if the path is a PHP stream URL, false otherwise. - */ -function is_stream( $path ) { - return Path::is_stream( $path ); -} - -/** - * Normalize a filesystem path. - * - * On Windows systems, replaces backslashes with forward slashes - * and forces upper-case drive letters. - * Allows for two leading slashes for Windows network shares, but - * ensures that all other duplicate slashes are reduced to a single one. - * Ensures upper-case drive letters on Windows systems. - * Allows for PHP file wrappers. - * - * @deprecated 2.13.0 Use Path::normalize() instead. - * - * @access public - * @category System - * - * @param string $path Path to normalize. - * @return string Normalized path. - */ -function normalize_path( $path ) { - return Path::normalize( $path ); -} - - -/** - * Convert Windows EOLs to *nix. - * - * @param string $str String to convert. - * @return string String with carriage return / newline pairs reduced to newlines. - */ -function normalize_eols( $str ) { - return str_replace( "\r\n", "\n", $str ); + return rtrim( $string, '/\\' ) . '/'; } /** @@ -1302,10 +788,10 @@ function get_temp_dir() { } // `sys_get_temp_dir()` introduced PHP 5.2.1. Will always return something. - $temp = Path::trailingslashit( sys_get_temp_dir() ); + $temp = trailingslashit( sys_get_temp_dir() ); if ( ! is_writable( $temp ) ) { - WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); + \WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); } return $temp; @@ -1322,25 +808,18 @@ function get_temp_dir() { * * @access public * - * @param string $url - * @param int $component * @return mixed - * - * @phpstan-return ($component is non-negative-int ? string|null : array{scheme?: string, user?: string, host?: string, port?: string, path?: string}) */ function parse_ssh_url( $url, $component = -1 ) { - preg_match( '#^((docker|docker\-compose|docker\-compose\-run|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); - /** - * @var array{scheme?: string, user?: string, host?: string, port?: string, path?: string} $bits - */ - $bits = []; - foreach ( [ + preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); + $bits = array(); + foreach ( array( 2 => 'scheme', 4 => 'user', 5 => 'host', 7 => 'port', 8 => 'path', - ] as $i => $key ) { + ) as $i => $key ) { if ( ! empty( $matches[ $i ] ) ) { $bits[ $key ] = $matches[ $i ]; } @@ -1348,9 +827,12 @@ function parse_ssh_url( $url, $component = -1 ) { // Find the hostname from `vagrant ssh-config` automatically. if ( preg_match( '/^vagrant:?/', $url ) ) { - if ( isset( $bits['host'] ) && 'vagrant' === $bits['host'] && empty( $bits['scheme'] ) ) { - $bits['scheme'] = 'vagrant'; - $bits['host'] = ''; + if ( 'vagrant' === $bits['host'] && empty( $bits['scheme'] ) ) { + $ssh_config = shell_exec( 'vagrant ssh-config 2>/dev/null' ); + if ( preg_match( '/Host\s(.+)/', $ssh_config, $matches ) ) { + $bits['scheme'] = 'vagrant'; + $bits['host'] = $matches[1]; + } } } @@ -1376,17 +858,16 @@ function parse_ssh_url( $url, $component = -1 ) { * @access public * @category Input * - * @param string $noun Resource being affected (e.g. plugin). - * @param string $verb Type of action happening to the noun (e.g. activate). + * @param string $noun Resource being affected (e.g. plugin) + * @param string $verb Type of action happening to the noun (e.g. activate) * @param integer $total Total number of resource being affected. * @param integer $successes Number of successful operations. * @param integer $failures Number of failures. * @param null|integer $skips Optional. Number of skipped operations. Default null (don't show skips). - * @return void */ function report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips = null ) { - $plural_noun = $noun . 's'; - $past_tense_verb = past_tense_verb( $verb ); + $plural_noun = $noun . 's'; + $past_tense_verb = past_tense_verb( $verb ); $past_tense_verb_upper = ucfirst( $past_tense_verb ); if ( $failures ) { $failed_skipped_message = null === $skips ? '' : " ({$failures} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')'; @@ -1413,47 +894,28 @@ function report_batch_operation_results( $noun, $verb, $total, $successes, $fail * @category Input * * @param string $arguments - * @return array + * @return array */ function parse_str_to_argv( $arguments ) { - preg_match_all( '/(?:--[^\s=]+=(["\'])((\\{2})*|[^\\\\](?:\\{2})*|(?:[^\1]+?[^\\\\](\\{2})*))\1|--[^\s=]+=[^\s]+|--[^\s=]+|(["\'])((\\{2})*|[^\\\\](?:\\{2})*|(?:[^\5]+?[^\\\\](\\{2})*))\5|[^\s]+)/', $arguments, $matches, PREG_SET_ORDER ); - $argv = []; - foreach ( $matches as $match ) { - // Check if this is a quoted associative argument (--key="value" or --key='value'). - // For associative args, groups 1 and 2 contain the quote char and value. - // For positional args, groups 5 and 6 contain the quote char and value, and group 1 is empty. - if ( isset( $match[1], $match[2] ) && 0 < strlen( $match[1] ) ) { - // Extract the key part (everything before the quote). - if ( preg_match( '/^(--[^=]+=)/', $match[0], $key_match ) ) { - $value = $match[2]; - // Unescape the quote character that was used to wrap the value. - $quote_char = $match[1]; - $value = str_replace( '\\' . $quote_char, $quote_char, $value ); - // Reconstruct without the outer quotes. - $argv[] = $key_match[1] . $value; - } else { - $argv[] = $match[0]; + preg_match_all( '/(?<=^|\s)([\'"]?)(.+?)(? $paths Single path as a string, or an array of paths. - * @param int|'default' $flags Optional. Flags to pass to glob. Defaults to GLOB_BRACE. - * @return array Expanded paths. + * @param string|array $paths Single path as a string, or an array of paths. + * @param int $flags Optional. Flags to pass to glob. Defaults to GLOB_BRACE. + * + * @return array Expanded paths. */ function expand_globs( $paths, $flags = 'default' ) { // Compatibility for systems without GLOB_BRACE. @@ -1519,16 +974,13 @@ function expand_globs( $paths, $flags = 'default' ) { } } - $expanded = []; + $expanded = array(); foreach ( (array) $paths as $path ) { - $matching = [ $path ]; + $matching = array( $path ); if ( preg_match( '/[' . preg_quote( '*?[]{}!', '/' ) . ']/', $path ) ) { - /** - * @var int $flags - */ - $matching = $glob_func( $path, $flags ) ?: []; + $matching = $glob_func( $path, $flags ) ?: array(); } $expanded = array_merge( $expanded, $matching ); } @@ -1540,38 +992,39 @@ function expand_globs( $paths, $flags = 'default' ) { * Simulate a `glob()` with the `GLOB_BRACE` flag set. For systems (eg Alpine Linux) built against a libc library (eg https://www.musl-libc.org/) that lacks it. * Copied and adapted from Zend Framework's `Glob::fallbackGlob()` and Glob::nextBraceSub()`. * - * Zend Framework (https://framework.zend.com/) + * Zend Framework (http://framework.zend.com/) + * + * @link http://github.com/zendframework/zf2 for the canonical source repository + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License * - * @link https://github.com/zendframework/zf2 for the canonical source repository - * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (https://www.zend.com) - * @license https://framework.zend.com/license/new-bsd New BSD License + * @param string $pattern Filename pattern. + * @param void $dummy_flags Not used. * - * @param string $pattern Filename pattern. - * @param void $dummy_flags Not used. - * @return array Array of paths. + * @return array Array of paths. */ -function glob_brace( $pattern, $dummy_flags = null ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $dummy_flags is needed for compatibility with the libc implementation. +function glob_brace( $pattern, $dummy_flags = null ) { static $next_brace_sub; if ( ! $next_brace_sub ) { // Find the end of the subpattern in a brace expression. $next_brace_sub = function ( $pattern, $current ) { - $length = strlen( $pattern ); - $depth = 0; + $length = strlen( $pattern ); + $depth = 0; while ( $current < $length ) { if ( '\\' === $pattern[ $current ] ) { if ( ++$current === $length ) { break; } - ++$current; + $current++; } else { if ( ( '}' === $pattern[ $current ] && 0 === $depth-- ) || ( ',' === $pattern[ $current ] && 0 === $depth ) ) { break; } if ( '{' === $pattern[ $current++ ] ) { - ++$depth; + $depth++; } } } @@ -1582,47 +1035,39 @@ function glob_brace( $pattern, $dummy_flags = null ) { // phpcs:ignore Generic.C $length = strlen( $pattern ); - $begin = 0; - // Find first opening brace. - // @phpstan-ignore for.variableOverwrite for ( $begin = 0; $begin < $length; $begin++ ) { if ( '\\' === $pattern[ $begin ] ) { - ++$begin; + $begin++; } elseif ( '{' === $pattern[ $begin ] ) { break; } } // Find comma or matching closing brace. - $next = $next_brace_sub( $pattern, $begin + 1 ); - if ( null === $next ) { - $result = glob( $pattern ); - return $result ?: []; + if ( null === ( $next = $next_brace_sub( $pattern, $begin + 1 ) ) ) { + return glob( $pattern ); } $rest = $next; // Point `$rest` to matching closing brace. while ( '}' !== $pattern[ $rest ] ) { - $rest = $next_brace_sub( $pattern, $rest + 1 ); - if ( null === $rest ) { - $result = glob( $pattern ); - return $result ?: []; + if ( null === ( $rest = $next_brace_sub( $pattern, $rest + 1 ) ) ) { + return glob( $pattern ); } } - $paths = []; - $p = $begin + 1; + $paths = array(); + $p = $begin + 1; // For each comma-separated subpattern. do { $subpattern = substr( $pattern, 0, $begin ) - . substr( $pattern, $p, $next - $p ) - . substr( $pattern, $rest + 1 ); + . substr( $pattern, $p, $next - $p ) + . substr( $pattern, $rest + 1 ); - $result = glob_brace( $subpattern ); - if ( ! empty( $result ) ) { + if ( $result = glob_brace( $subpattern ) ) { $paths = array_merge( $paths, $result ); } @@ -1638,7 +1083,7 @@ function glob_brace( $pattern, $dummy_flags = null ) { // phpcs:ignore Generic.C } /** - * Get the closest suggestion for a mistyped target term amongst a list of + * Get the closest suggestion for a mis-typed target term amongst a list of * options. * * Uses the Levenshtein algorithm to calculate the relative "distance" between @@ -1647,35 +1092,33 @@ function glob_brace( $pattern, $dummy_flags = null ) { // phpcs:ignore Generic.C * If the "distance" to the closest term is higher than the threshold, an empty * string is returned. * - * @param string $target Target term to get a suggestion for. - * @param array $options Array with possible options. - * @param int $threshold Threshold above which to return an empty string. + * @param string $target Target term to get a suggestion for. + * @param array $options Array with possible options. + * @param int $threshold Threshold above which to return an empty string. + * * @return string */ function get_suggestion( $target, array $options, $threshold = 2 ) { - $suggestion_map = [ - 'add' => 'create', - 'check' => 'check-update', - 'capability' => 'cap', - 'clear' => 'flush', - 'decrement' => 'decr', - 'del' => 'delete', - 'directory' => 'dir', - 'exec' => 'eval', - 'exec-file' => 'eval-file', - 'increment' => 'incr', - 'language' => 'locale', - 'lang' => 'locale', - 'new' => 'create', - 'number' => 'count', - 'remove' => 'delete', - 'regen' => 'regenerate', - 'rep' => 'replace', - 'repl' => 'replace', - 'trash' => 'delete', - 'v' => 'version', - ]; + $suggestion_map = array( + 'check' => 'check-update', + 'clear' => 'flush', + 'decrement' => 'decr', + 'del' => 'delete', + 'directory' => 'dir', + 'exec' => 'eval', + 'exec-file' => 'eval-file', + 'increment' => 'incr', + 'language' => 'locale', + 'lang' => 'locale', + 'new' => 'create', + 'number' => 'count', + 'remove' => 'delete', + 'regen' => 'regenerate', + 'rep' => 'replace', + 'repl' => 'replace', + 'v' => 'version', + ); if ( array_key_exists( $target, $suggestion_map ) && in_array( $suggestion_map[ $target ], $options, true ) ) { return $suggestion_map[ $target ]; @@ -1684,11 +1127,8 @@ function get_suggestion( $target, array $options, $threshold = 2 ) { if ( empty( $options ) ) { return ''; } - - $levenshtein = []; - foreach ( $options as $option ) { - $distance = levenshtein( $option, $target ); + $distance = levenshtein( $option, $target ); $levenshtein[ $option ] = $distance; } @@ -1713,13 +1153,52 @@ function get_suggestion( $target, array $options, $threshold = 2 ) { * * Use the __FILE__ or __DIR__ constants as a starting point. * - * @deprecated 2.13.0 Use Path::phar_safe() instead. - * * @param string $path An absolute path that might be within a Phar. + * * @return string A Phar-safe version of the path. */ function phar_safe_path( $path ) { - return Path::phar_safe( $path ); + + if ( ! inside_phar() ) { + return $path; + } + + return str_replace( + PHAR_STREAM_PREFIX . WP_CLI_PHAR_PATH . '/', + PHAR_STREAM_PREFIX, + $path + ); +} + +/** + * Check whether a given Command object is part of the bundled set of + * commands. + * + * This function accepts both a fully qualified class name as a string as + * well as an object that extends `WP_CLI\Dispatcher\CompositeCommand`. + * + * @param \WP_CLI\Dispatcher\CompositeCommand|string $command + * + * @return bool + */ +function is_bundled_command( $command ) { + static $classes; + + if ( null === $classes ) { + $classes = array(); + $class_map = WP_CLI_VENDOR_DIR . '/composer/autoload_commands_classmap.php'; + if ( file_exists( WP_CLI_VENDOR_DIR . '/composer/' ) ) { + $classes = include $class_map; + } + } + + if ( is_object( $command ) ) { + $command = get_class( $command ); + } + + return is_string( $command ) + ? array_key_exists( $command, $classes ) + : false; } /** @@ -1727,17 +1206,20 @@ function phar_safe_path( $path ) { * Removes (if there) if Windows, adds (if not there) if not. * * @param string $command + * * @return string */ function force_env_on_nix_systems( $command ) { - $env_prefix = '/usr/bin/env '; + $env_prefix = '/usr/bin/env '; $env_prefix_len = strlen( $env_prefix ); if ( is_windows() ) { if ( 0 === strncmp( $command, $env_prefix, $env_prefix_len ) ) { $command = substr( $command, $env_prefix_len ); } - } elseif ( 0 !== strncmp( $command, $env_prefix, $env_prefix_len ) ) { - $command = $env_prefix . $command; + } else { + if ( 0 !== strncmp( $command, $env_prefix, $env_prefix_len ) ) { + $command = $env_prefix . $command; + } } return $command; } @@ -1747,6 +1229,7 @@ function force_env_on_nix_systems( $command ) { * * @param string $context Optional. If set will appear in error message. Default null. * @param bool $return Optional. If set will return false rather than error out. Default false. + * * @return bool */ function check_proc_available( $context = null, $return = false ) { @@ -1768,12 +1251,13 @@ function check_proc_available( $context = null, $return = false ) { * Returns past tense of verb, with limited accuracy. Only regular verbs catered for, apart from "reset". * * @param string $verb Verb to return past tense of. + * * @return string */ function past_tense_verb( $verb ) { - static $irregular = [ + static $irregular = array( 'reset' => 'reset', - ]; + ); if ( isset( $irregular[ $verb ] ) ) { return $irregular[ $verb ]; } @@ -1800,22 +1284,30 @@ function past_tense_verb( $verb ) { * @return string */ function get_php_binary() { - // Phar installs always use PHP_BINARY. - if ( Path::inside_phar() ) { + if ( $wp_cli_php_used = getenv( 'WP_CLI_PHP_USED' ) ) { + return $wp_cli_php_used; + } + + if ( $wp_cli_php = getenv( 'WP_CLI_PHP' ) ) { + return $wp_cli_php; + } + + // Available since PHP 5.4. + if ( defined( 'PHP_BINARY' ) ) { return PHP_BINARY; } - $wp_cli_php_used = getenv( 'WP_CLI_PHP_USED' ); - if ( false !== $wp_cli_php_used ) { - return $wp_cli_php_used; + // @codingStandardsIgnoreLine + if ( @is_executable( PHP_BINDIR . '/php' ) ) { + return PHP_BINDIR . '/php'; } - $wp_cli_php = getenv( 'WP_CLI_PHP' ); - if ( false !== $wp_cli_php ) { - return $wp_cli_php; + // @codingStandardsIgnoreLine + if ( is_windows() && @is_executable( PHP_BINDIR . '/php.exe' ) ) { + return PHP_BINDIR . '/php.exe'; } - return PHP_BINARY; + return 'php'; } /** @@ -1824,30 +1316,19 @@ function get_php_binary() { * * @access public * - * @param string $cmd Command to execute. - * @param array|resource> $descriptorspec Indexed array of descriptor numbers and their values. - * @param array &$pipes Indexed array of file pointers that correspond to PHP's end of any pipes that are created. - * @param string $cwd Initial working directory for the command. - * @param array $env Array of environment variables. - * @param array|null $other_options Array of additional options (Windows only). - * @return resource|false Command stripped of any environment variable settings, or false on failure. + * @param string $command Command to execute. + * @param array $descriptorspec Indexed array of descriptor numbers and their values. + * @param array &$pipes Indexed array of file pointers that correspond to PHP's end of any pipes that are created. + * @param string $cwd Initial working directory for the command. + * @param array $env Array of environment variables. + * @param array $other_options Array of additional options (Windows only). * - * @param-out array $pipes + * @return string Command stripped of any environment variable settings. */ function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = null, $other_options = null ) { if ( is_windows() ) { - // @phpstan-ignore no.private.function - $cmd = _proc_open_compat_win_env( $cmd, $env ); - - // Normalize forward slashes in the executable name for Windows cmd.exe - if ( false !== strpos( $cmd, '/' ) ) { - if ( preg_match( '/^("[^"]*"|[^ ]+)/', $cmd, $matches ) ) { - $executable = $matches[0]; - $rest = substr( $cmd, strlen( $executable ) ); - $executable = str_replace( '/', '\\', $executable ); - $cmd = $executable . $rest; - } - } + // Need to encompass the whole command in double quotes - PHP bug https://bugs.php.net/bug.php?id=49139 + $cmd = '"' . _proc_open_compat_win_env( $cmd, $env ) . '"'; } return proc_open( $cmd, $descriptorspec, $pipes, $cwd, $env, $other_options ); } @@ -1858,8 +1339,9 @@ function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = n * * @access private * - * @param string $cmd Command to execute. - * @param array|null &$env Array of existing environment variables. Will be modified if any settings in command. + * @param string $command Command to execute. + * @param array &$env Array of existing environment variables. Will be modified if any settings in command. + * * @return string Command stripped of any environment variable settings. */ function _proc_open_compat_win_env( $cmd, &$env ) { @@ -1867,7 +1349,7 @@ function _proc_open_compat_win_env( $cmd, &$env ) { while ( preg_match( '/^([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|[^ ]*) /', $cmd, $matches ) ) { $cmd = substr( $cmd, strlen( $matches[0] ) ); if ( null === $env ) { - $env = []; + $env = array(); } $env[ $matches[1] ] = isset( $matches[2][0] ) && '"' === $matches[2][0] ? substr( $matches[2], 1, -1 ) : $matches[2]; } @@ -1890,57 +1372,24 @@ function _proc_open_compat_win_env( $cmd, &$env ) { * or real_escape next. */ function esc_like( $text ) { - /** - * @var null|\wpdb $wpdb - */ - global $wpdb; - - // Check if the esc_like() method exists on the global $wpdb object. - // We need to do this because to ensure compatibility layers like the - // SQLite integration plugin still work. - if ( null !== $wpdb && method_exists( $wpdb, 'esc_like' ) ) { - return $wpdb->esc_like( $text ); - } - return addcslashes( $text, '_%\\' ); } -/** - * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names. - * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html - * - * @param string|array $idents A single identifier or an array of identifiers. - * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings. - * - * @phpstan-return ($idents is string ? string : array) - */ -function esc_sql_ident( $idents ) { - $backtick = static function ( $v ) { - // Escape any backticks in the identifier by doubling. - return '`' . str_replace( '`', '``', $v ) . '`'; - }; - if ( is_string( $idents ) ) { - return $backtick( $idents ); - } - return array_map( $backtick, $idents ); -} - /** * Check whether a given string is a valid JSON representation. * - * @param mixed $argument String to evaluate. + * @param string $argument String to evaluate. * @param bool $ignore_scalars Optional. Whether to ignore scalar values. * Defaults to true. - * @return bool Whether the provided string is a valid JSON representation. * - * @phpstan-assert-if-true =non-empty-string $argument + * @return bool Whether the provided string is a valid JSON representation. */ function is_json( $argument, $ignore_scalars = true ) { - if ( ! is_string( $argument ) || '' === $argument ) { + if ( empty( $argument ) || ! is_string( $argument ) ) { return false; } - if ( $ignore_scalars && ! in_array( $argument[0], [ '{', '[' ], true ) ) { + if ( $ignore_scalars && ! in_array( $argument[0], array( '{', '[' ), true ) ) { return false; } @@ -1952,10 +1401,11 @@ function is_json( $argument, $ignore_scalars = true ) { /** * Parse known shell arrays included in the $assoc_args array. * - * @param array $assoc_args Associative array of arguments. - * @param array $array_arguments Array of argument keys that should receive an - * array through the shell. - * @return array + * @param array $assoc_args Associative array of arguments. + * @param array $array_arguments Array of argument keys that should receive an + * array through the shell. + * + * @return array */ function parse_shell_arrays( $assoc_args, $array_arguments ) { if ( empty( $assoc_args ) || empty( $array_arguments ) ) { @@ -1964,451 +1414,9 @@ function parse_shell_arrays( $assoc_args, $array_arguments ) { foreach ( $array_arguments as $key ) { if ( array_key_exists( $key, $assoc_args ) && is_json( $assoc_args[ $key ] ) ) { - // @phpstan-ignore cast.useless - $assoc_args[ $key ] = json_decode( (string) $assoc_args[ $key ], $assoc = true ); + $assoc_args[ $key ] = json_decode( $assoc_args[ $key ], $assoc = true ); } } return $assoc_args; } - -/** - * Describe a callable as a string. - * - * @param callable $callable The callable to describe. - * @return string String description of the callable. - */ -function describe_callable( $callable ) { - try { - if ( $callable instanceof Closure ) { - $reflection = new ReflectionFunction( $callable ); - - return "Closure in file {$reflection->getFileName()} at line {$reflection->getStartLine()}"; - } - - if ( is_array( $callable ) ) { - if ( is_object( $callable[0] ) ) { - return sprintf( - '%s->%s()', - get_class( $callable[0] ), - (string) $callable[1] - ); - } - - return sprintf( '%s::%s()', (string) $callable[0], (string) $callable[1] ); - } - - return gettype( $callable ); - } catch ( Exception $exception ) { - return 'Callable of unknown type'; - } -} - -/** - * Checks if the given class and method pair is a valid callable. - * - * This accommodates changes to `is_callable()` in PHP 8 that mean an array of a - * classname and instance method is no longer callable. - * - * @param array $pair The class and method pair to check. - * @return bool - */ -function is_valid_class_and_method_pair( $pair ) { - if ( ! is_array( $pair ) || 2 !== count( $pair ) ) { - return false; - } - - if ( ! is_string( $pair[0] ) || ! is_string( $pair[1] ) ) { - return false; - } - - if ( ! class_exists( $pair[0] ) ) { - return false; - } - - if ( ! method_exists( $pair[0], $pair[1] ) ) { - return false; - } - - return true; -} - -/** - * Pluralizes a noun in a grammatically correct way. - * - * @param string $noun Noun to be pluralized. Needs to be in singular form. - * @param int|null $count Optional. Count of the nouns, to decide whether to - * pluralize. Will pluralize unconditionally if none - * provided. - * @return string Pluralized noun. - */ -function pluralize( $noun, $count = null ) { - if ( 1 === $count ) { - return $noun; - } - - return Inflector::pluralize( $noun ); -} - -/** - * Return the detected database type. - * - * Can be either 'sqlite' (if in a WordPress installation with the SQLite drop-in), - * 'mysql', or 'mariadb'. - * - * @return string Database type. - */ -function get_db_type() { - static $db_type = null; - - if ( defined( 'SQLITE_DB_DROPIN_VERSION' ) ) { - return 'sqlite'; - } - - if ( null !== $db_type ) { - return $db_type; - } - - $db_type = 'mysql'; - - $binary = get_mysql_binary_path(); - - if ( '' !== $binary ) { - $result = Process::create( "$binary --version", null, null )->run(); - - if ( 0 === $result->return_code ) { - $db_type = ( false !== strpos( $result->stdout, 'MariaDB' ) ) ? 'mariadb' : 'mysql'; - } - } - - return $db_type; -} - -/** - * Get the path to the MySQL or MariaDB binary. - * - * If the MySQL binary is provided by MariaDB (as determined by the version string), - * prefers the actual MariaDB binary. - * - * @since 2.12.0 Now also checks for MariaDB. - * - * @return string Path to the MySQL/MariaDB binary, or an empty string if not found. - */ -function get_mysql_binary_path() { - static $path = null; - - if ( null !== $path ) { - return $path; - } - - $path = ''; - $mysql = Process::create( '/usr/bin/env which mysql', null, null )->run(); - $mariadb = Process::create( '/usr/bin/env which mariadb', null, null )->run(); - - $mysql_binary = trim( $mysql->stdout ); - $mariadb_binary = trim( $mariadb->stdout ); - - if ( 0 === $mysql->return_code ) { - if ( '' !== $mysql_binary ) { - $path = $mysql_binary; - $result = Process::create( "$mysql_binary --version", null, null )->run(); - - // It's actually MariaDB disguised as MySQL. - if ( 0 === $result->return_code && false !== strpos( $result->stdout, 'MariaDB' ) && 0 === $mariadb->return_code ) { - $path = $mariadb_binary; - } - } - } elseif ( 0 === $mariadb->return_code ) { - $path = $mariadb_binary; - } - - return $path; -} - -/** - * Get the version of the MySQL or MariaDB database. - * - * @since 2.12.0 Now also checks for MariaDB. - * - * @return string Version of the MySQL/MariaDB database, - * or an empty string if not found. - */ -function get_mysql_version() { - static $version = null; - - if ( null !== $version ) { - return $version; - } - - $version = ''; - - $db_type = get_db_type(); - - if ( 'sqlite' !== $db_type ) { - $result = Process::create( "/usr/bin/env $db_type --version", null, null )->run(); - - if ( 0 === $result->return_code ) { - $version = trim( $result->stdout ); - } - } - - return $version; -} - -/** - * Returns the correct `dump` command based on the detected database type. - * - * For MariaDB, prefers `mariadb-dump` (available since MariaDB 10.5) but falls - * back to `mysqldump` if the command is not found on the system. - * - * @return string The appropriate dump command. - */ -function get_sql_dump_command() { - static $command = null; - - if ( null !== $command ) { - return $command; - } - - $command = 'mysqldump'; - - if ( 'mariadb' === get_db_type() ) { - $result = Process::create( '/usr/bin/env which mariadb-dump', null, null )->run(); - - if ( 0 === $result->return_code && '' !== trim( $result->stdout ) ) { - $command = 'mariadb-dump'; - } - } - - return $command; -} - -/** - * Returns the correct `check` command based on the detected database type. - * - * For MariaDB, prefers `mariadb-check` (available since MariaDB 10.5) but falls - * back to `mysqlcheck` if the command is not found on the system. - * - * @return string The appropriate check command. - */ -function get_sql_check_command() { - static $command = null; - - if ( null !== $command ) { - return $command; - } - - $command = 'mysqlcheck'; - - if ( 'mariadb' === get_db_type() ) { - $result = Process::create( '/usr/bin/env which mariadb-check', null, null )->run(); - - if ( 0 === $result->return_code && '' !== trim( $result->stdout ) ) { - $command = 'mariadb-check'; - } - } - - return $command; -} - -/** - * Get the SQL modes of the MySQL session. - * - * @return string[] Array of SQL modes, or an empty array if they couldn't be - * read. - */ -function get_sql_modes() { - static $sql_modes = null; - - if ( null !== $sql_modes ) { - return $sql_modes; - } - - $binary = get_mysql_binary_path(); - - if ( '' === $binary ) { - $sql_modes = []; - } else { - $result = Process::create( "$binary --no-auto-rehash --batch --skip-column-names --execute=\"SELECT @@SESSION.sql_mode\"", null, null )->run(); - - if ( 0 !== $result->return_code ) { - $sql_modes = []; - } else { - $split_lines = preg_split( "/\r\n|\n|\r/", $result->stdout ); - $sql_modes = array_filter( - array_map( - 'trim', - $split_lines ?: [] - ) - ); - } - } - - return $sql_modes; -} - -/** - * Get an environment variable value, with config file fallback. - * - * Checks the actual environment variable first, then falls back to - * values defined in the 'env' configuration key in wp-cli.yml. - * - * @param string $name Environment variable name. - * @return string|false The value of the environment variable, or false if not set. - */ -function get_env_or_config( $name ) { - $env_value = getenv( $name ); - if ( false !== $env_value ) { - return $env_value; - } - - // Try to get from config file - $runner = WP_CLI::get_runner(); - if ( $runner && isset( $runner->extra_config['env'] ) && is_array( $runner->extra_config['env'] ) && isset( $runner->extra_config['env'][ $name ] ) ) { - // @phpstan-ignore cast.string - return (string) $runner->extra_config['env'][ $name ]; - } - - return false; -} - -/** - * Get the WP-CLI cache directory. - * - * @return string - */ -function get_cache_dir() { - $home = Path::get_home_dir(); - $cache_dir = get_env_or_config( 'WP_CLI_CACHE_DIR' ); - return $cache_dir ? : "$home/.wp-cli/cache"; -} - -/** - * Check whether any input is passed to STDIN. - * - * @return bool - */ -function has_stdin() { - // Use fstat() to detect character devices (S_IFCHR), which includes - // both interactive terminals (TTY) and /dev/null. In non-interactive - // environments (cron, atd, puppet exec), STDIN is often connected to - // /dev/null, which stream_select() incorrectly reports as readable - // (since EOF is immediately available). For the purposes of this - // helper, character devices are treated as "no stdin" to avoid - // blocking on interactive input or misdetecting /dev/null as input. - $stat = fstat( STDIN ); - if ( false !== $stat ) { - // S_IFMT (0170000): bitmask to extract the POSIX file type. - // S_IFCHR (0020000): file type constant for character devices. - // Character devices include both interactive terminals (TTY) and - // /dev/null, all of which are treated as not providing stdin here. - if ( 0020000 === ( $stat['mode'] & 0170000 ) ) { - return false; - } - } - - $handle = fopen( 'php://stdin', 'r' ); - if ( ! $handle ) { - return false; - } - - $read = array( $handle ); - $write = null; - $except = null; - $streams = stream_select( $read, $write, $except, 0 ); - - fclose( $handle ); - - return 1 === $streams; -} - -/** - * Return description of WP_CLI hooks used in @when tag - * - * @param string $hook Name of WP_CLI hook - * - * @return string|null - */ -function get_hook_description( $hook ) { - $events = [ - 'find_command_to_run_pre' => 'just before WP-CLI finds the command to run.', - 'before_registering_contexts' => 'before the contexts are registered.', - 'before_wp_load' => 'just before the WP load process begins.', - 'before_wp_config_load' => 'after wp-config.php has been located.', - 'after_wp_config_load' => 'after wp-config.php has been loaded into scope.', - 'after_wp_load' => 'just after the WP load process has completed.', - ]; - - if ( array_key_exists( $hook, $events ) ) { - return $events[ $hook ]; - } - return null; -} - -/** - * Escape a value for CSV output. - * - * Values that start with the following characters are escaping with a single - * quote: =, +, -, @, TAB (0x09) and CR (0x0D). - * - * @param string $value Value to escape. - * @return string Escaped value. - */ -function escape_csv_value( $value ) { - if ( null === $value ) { - return ''; - } - - // Convert to string if not already - $value = (string) $value; - - if ( - in_array( - substr( $value, 0, 1 ), - [ '=', '+', '-', '@', "\t", "\r" ], - true - ) - ) { - return "'{$value}"; - } - - return $value; -} - -/** - * Convert a size in bytes to a human-readable format. - * - * @param int|float $bytes Size in bytes. - * @param int $decimals Optional. Number of decimal places to round to. Default 0. - * @param string $unit Optional. Specific unit to use. Default is auto-detect. - * @return string Human-readable size. - */ -function format_bytes_string( $bytes, $decimals = 0, $unit = '' ) { - if ( 0 === (int) $bytes ) { - return '0 B'; - } - - $sizes = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; - - // Use absolute value for calculating log exponent metrics cleanly. - $abs_bytes = abs( (float) $bytes ); - - // Resolve the specific target unit manually. - $size_key = false; - if ( ! empty( $unit ) ) { - $unit = strtoupper( $unit ); - $size_key = array_search( $unit, $sizes, true ); - } - - // Calculate and bound the auto-detect unit size string if no valid unit was requested. - if ( false === $size_key ) { - $size_key = (int) floor( log( $abs_bytes ) / log( 1000 ) ); - $size_key = min( $size_key, count( $sizes ) - 1 ); // Prevent out of bounds - - $unit = $sizes[ $size_key ]; - } - - $divisor = pow( 1000, $size_key ); - - return round( $bytes / $divisor, $decimals ) . ' ' . $unit; -} diff --git a/php/wp-cli.php b/php/wp-cli.php index 077695669..afd00d75d 100644 --- a/php/wp-cli.php +++ b/php/wp-cli.php @@ -1,35 +1,23 @@ error ) ) { + wp_die( $wpdb->error ); +} + +// Set the database table prefix and the format specifiers for database table columns. +// @codingStandardsIgnoreStart +$GLOBALS['table_prefix'] = $table_prefix; +// @codingStandardsIgnoreEnd +wp_set_wpdb_vars(); + +// Start the WordPress object cache, or an external object cache if the drop-in is present. +wp_start_object_cache(); + +// Attach the default filters. +require ABSPATH . WPINC . '/default-filters.php'; + +// Initialize multisite if enabled. +if ( is_multisite() ) { + Utils\maybe_require( '4.6-alpha-37575', ABSPATH . WPINC . '/class-wp-site-query.php' ); + Utils\maybe_require( '4.6-alpha-37896', ABSPATH . WPINC . '/class-wp-network-query.php' ); + require ABSPATH . WPINC . '/ms-blogs.php'; + require ABSPATH . WPINC . '/ms-settings.php'; +} elseif ( ! defined( 'MULTISITE' ) ) { + define( 'MULTISITE', false ); +} + +register_shutdown_function( 'shutdown_action_hook' ); + +// Stop most of WordPress from being loaded if we just want the basics. +if ( SHORTINIT ) { + return false; +} + +// Load the L10n library. +require_once ABSPATH . WPINC . '/l10n.php'; + +// WP-CLI: Permit Utils\wp_not_installed() to run on < WP 4.0 +apply_filters( 'nocache_headers', array() ); + +// Run the installer if WordPress is not installed. +wp_not_installed(); + +// Load most of WordPress. +require ABSPATH . WPINC . '/class-wp-walker.php'; +require ABSPATH . WPINC . '/class-wp-ajax-response.php'; +require ABSPATH . WPINC . '/formatting.php'; +require ABSPATH . WPINC . '/capabilities.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-roles.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-role.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-user.php' ); +require ABSPATH . WPINC . '/query.php'; +Utils\maybe_require( '3.7-alpha-25139', ABSPATH . WPINC . '/date.php' ); +require ABSPATH . WPINC . '/theme.php'; +require ABSPATH . WPINC . '/class-wp-theme.php'; +require ABSPATH . WPINC . '/template.php'; +require ABSPATH . WPINC . '/user.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-user-query.php' ); +Utils\maybe_require( '4.0', ABSPATH . WPINC . '/session.php' ); +require ABSPATH . WPINC . '/meta.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-meta-query.php' ); +Utils\maybe_require( '4.5-alpha-35776', ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php' ); +require ABSPATH . WPINC . '/general-template.php'; +require ABSPATH . WPINC . '/link-template.php'; +require ABSPATH . WPINC . '/author-template.php'; +require ABSPATH . WPINC . '/post.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-page.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-page-dropdown.php' ); +Utils\maybe_require( '4.6-alpha-37890', ABSPATH . WPINC . '/class-wp-post-type.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-post.php' ); +require ABSPATH . WPINC . '/post-template.php'; +Utils\maybe_require( '3.6-alpha-23451', ABSPATH . WPINC . '/revision.php' ); +Utils\maybe_require( '3.6-alpha-23451', ABSPATH . WPINC . '/post-formats.php' ); +require ABSPATH . WPINC . '/post-thumbnail-template.php'; +require ABSPATH . WPINC . '/category.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-category.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-category-dropdown.php' ); +require ABSPATH . WPINC . '/category-template.php'; +require ABSPATH . WPINC . '/comment.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-comment.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-comment-query.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-comment.php' ); +require ABSPATH . WPINC . '/comment-template.php'; +require ABSPATH . WPINC . '/rewrite.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-rewrite.php' ); +require ABSPATH . WPINC . '/feed.php'; +require ABSPATH . WPINC . '/bookmark.php'; +require ABSPATH . WPINC . '/bookmark-template.php'; +require ABSPATH . WPINC . '/kses.php'; +require ABSPATH . WPINC . '/cron.php'; +require ABSPATH . WPINC . '/deprecated.php'; +require ABSPATH . WPINC . '/script-loader.php'; +require ABSPATH . WPINC . '/taxonomy.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-term.php' ); +Utils\maybe_require( '4.6-alpha-37575', ABSPATH . WPINC . '/class-wp-term-query.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-tax-query.php' ); +require ABSPATH . WPINC . '/update.php'; +require ABSPATH . WPINC . '/canonical.php'; +require ABSPATH . WPINC . '/shortcodes.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/embed.php' ); +require ABSPATH . WPINC . '/class-wp-embed.php'; +require ABSPATH . WPINC . '/media.php'; +Utils\maybe_require( '4.4-alpha-34903', ABSPATH . WPINC . '/class-wp-oembed-controller.php' ); +require ABSPATH . WPINC . '/http.php'; +require_once ABSPATH . WPINC . '/class-http.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-streams.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-curl.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-proxy.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-cookie.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-encoding.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-response.php' ); +Utils\maybe_require( '4.6-alpha-37438', ABSPATH . WPINC . '/class-wp-http-requests-response.php' ); +require ABSPATH . WPINC . '/widgets.php'; +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-widget.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-widget-factory.php' ); +require ABSPATH . WPINC . '/nav-menu.php'; +require ABSPATH . WPINC . '/nav-menu-template.php'; +require ABSPATH . WPINC . '/admin-bar.php'; +Utils\maybe_require( '4.4-alpha-34928', ABSPATH . WPINC . '/rest-api.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' ); +Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' ); + +// Load multisite-specific files. +if ( is_multisite() ) { + require ABSPATH . WPINC . '/ms-functions.php'; + require ABSPATH . WPINC . '/ms-default-filters.php'; + require ABSPATH . WPINC . '/ms-deprecated.php'; +} + +// Define constants that rely on the API to obtain the default value. +// Define must-use plugin directory constants, which may be overridden in the sunrise.php drop-in. +wp_plugin_directory_constants(); + +$symlinked_plugins_supported = function_exists( 'wp_register_plugin_realpath' ); +if ( $symlinked_plugins_supported ) { + $GLOBALS['wp_plugin_paths'] = array(); +} + +// Load must-use plugins. +foreach ( wp_get_mu_plugins() as $mu_plugin ) { + include_once $mu_plugin; +} +unset( $mu_plugin ); + +// Load network activated plugins. +if ( is_multisite() ) { + foreach ( wp_get_active_network_plugins() as $network_plugin ) { + if ( $symlinked_plugins_supported ) { + wp_register_plugin_realpath( $network_plugin ); + } + include_once $network_plugin; + } + unset( $network_plugin ); +} + +do_action( 'muplugins_loaded' ); + +if ( is_multisite() ) { + ms_cookie_constants(); +} + +// Define constants after multisite is loaded. Cookie-related constants may be overridden in ms_network_cookies(). +wp_cookie_constants(); + +// Define and enforce our SSL constants +wp_ssl_constants(); + +// Create common globals. +require ABSPATH . WPINC . '/vars.php'; + +// Make taxonomies and posts available to plugins and themes. +// @plugin authors: warning: these get registered again on the init hook. +create_initial_taxonomies(); +create_initial_post_types(); + +// Register the default theme directory root +register_theme_directory( get_theme_root() ); + +// Load active plugins. +foreach ( wp_get_active_and_valid_plugins() as $plugin ) { + if ( $symlinked_plugins_supported ) { + wp_register_plugin_realpath( $plugin ); + } + include_once $plugin; +} +unset( $plugin, $symlinked_plugins_supported ); + +// Load pluggable functions. +require ABSPATH . WPINC . '/pluggable.php'; +require ABSPATH . WPINC . '/pluggable-deprecated.php'; + +// Set internal encoding. +wp_set_internal_encoding(); + +// Run wp_cache_postload() if object cache is enabled and the function exists. +if ( WP_CACHE && function_exists( 'wp_cache_postload' ) ) { + wp_cache_postload(); +} + +do_action( 'plugins_loaded' ); + +// Define constants which affect functionality if not already defined. +wp_functionality_constants(); + +// Add magic quotes and set up $_REQUEST ( $_GET + $_POST ) +wp_magic_quotes(); + +do_action( 'sanitize_comment_cookies' ); + +/** + * WordPress Query object + * @global object $wp_the_query + * @since 2.0.0 + */ +$GLOBALS['wp_the_query'] = new WP_Query(); + +/** + * Holds the reference to @see $wp_the_query + * Use this global for WordPress queries + * @global object $wp_query + * @since 1.5.0 + */ +$GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; + +/** + * Holds the WordPress Rewrite object for creating pretty URLs + * @global object $wp_rewrite + * @since 1.5.0 + */ +$GLOBALS['wp_rewrite'] = new WP_Rewrite(); + +/** + * WordPress Object + * @global object $wp + * @since 2.0.0 + */ +$GLOBALS['wp'] = new WP(); + +/** + * WordPress Widget Factory Object + * @global object $wp_widget_factory + * @since 2.8.0 + */ +$GLOBALS['wp_widget_factory'] = new WP_Widget_Factory(); + +/** + * WordPress User Roles + * @global object $wp_roles + * @since 2.0.0 + */ +$GLOBALS['wp_roles'] = new WP_Roles(); + +do_action( 'setup_theme' ); + +// Define the template related constants. +wp_templating_constants(); + +// Load the default text localization domain. +load_default_textdomain(); + +$locale = get_locale(); +$locale_file = WP_LANG_DIR . "/$locale.php"; +if ( ( 0 === validate_file( $locale ) ) && is_readable( $locale_file ) ) { + require $locale_file; +} +unset( $locale_file ); + +// Pull in locale data after loading text domain. +require_once ABSPATH . WPINC . '/locale.php'; + +/** + * WordPress Locale object for loading locale domain date and various strings. + * @global object $wp_locale + * @since 2.1.0 + */ +$GLOBALS['wp_locale'] = new WP_Locale(); + +// Load the functions for the active theme, for both parent and child theme if applicable. +global $pagenow; +if ( ! defined( 'WP_INSTALLING' ) || 'wp-activate.php' === $pagenow ) { + if ( TEMPLATEPATH !== STYLESHEETPATH && file_exists( STYLESHEETPATH . '/functions.php' ) ) { + include STYLESHEETPATH . '/functions.php'; + } + if ( file_exists( TEMPLATEPATH . '/functions.php' ) ) { + include TEMPLATEPATH . '/functions.php'; + } +} + +do_action( 'after_setup_theme' ); + +// Set up current user. +$GLOBALS['wp']->init(); + +/** + * Most of WP is loaded at this stage, and the user is authenticated. WP continues + * to load on the init hook that follows (e.g. widgets), and many plugins instantiate + * themselves on it for all sorts of reasons (e.g. they need a user, a taxonomy, etc.). + * + * If you wish to plug an action once WP is loaded, use the wp_loaded hook below. + */ +do_action( 'init' ); + +// Check site status +# if ( is_multisite() ) { // WP-CLI +if ( is_multisite() && ! defined( 'WP_INSTALLING' ) ) { + if ( true !== ( $file = ms_site_check() ) ) { + require $file; + die(); + } + unset( $file ); +} + +/** + * This hook is fired once WP, all plugins, and the theme are fully loaded and instantiated. + * + * AJAX requests should use wp-admin/admin-ajax.php. admin-ajax.php can handle requests for + * users not logged in. + * + * @link http://codex.wordpress.org/AJAX_in_Plugins + * + * @since 3.0.0 + */ +do_action( 'wp_loaded' ); diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 14a783144..e34339ca5 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -1,132 +1,43 @@ - Custom ruleset for WP-CLI + WordPress Coding Standards for WP-CLI - + + + + + + + + - . - - - */tests/data/* - */bundle/* - - - - - - - - - - - - - - - - - - - - */utils/phpstan/* - - - - - - - - - - - - - - - + */ci/* + */features/* + */packages/* + */tests/* + */utils/* + */vendor/* + + + + + + + + + + + + + + + + + - - - + + - - - - - - - - - - - - - */php/commands/src/CLI_(Cache_)?Command\.php$ - */php/commands/src/Help_Command\.php$ - - - - - php/wp-settings-cli\.php$ - - - - - */tests/(Extractor|Utils)Test\.php$ - - - - - */tests/(Process|Utils)Test\.php$ - - - - - */utils/get-package-require-from-composer\.php$ - - - - - */php/WP_CLI/Iterators/Transform\.php - */php/WP_CLI/Inflector\.php - */php/WP_CLI/Autoloader\.php - */php/WP_CLI/Dispatcher/CommandFactory\.php - */php/WP_CLI/Dispatcher/CompositeCommand\.php - */php/WP_CLI/Dispatcher/Subcommand\.php - */php/utils\.php - */php/utils-wp\.php - */php/WP_CLI/Runner\.php - */php/WP_CLI/UpgraderSkin\.php$ - */php/class-wp-cli\.php$ - - diff --git a/phpstan.neon.dist b/phpstan.neon.dist deleted file mode 100644 index 928b45861..000000000 --- a/phpstan.neon.dist +++ /dev/null @@ -1,46 +0,0 @@ -parameters: - level: 9 - paths: - - php/commands - - php/WP_CLI - - php/boot-fs.php - - php/bootstrap.php - - php/class-wp-cli.php - - php/class-wp-cli-command.php - - php/dispatcher.php - - php/utils.php - - php/utils-wp.php - - php/wp-cli.php - - tests - excludePaths: - - php/WP_CLI/ComposerIO.php - - php/WP_CLI/PackageManagerEventSubscriber.php - - tests/data - scanDirectories: - - bundle/rmccue/requests - - vendor/wp-cli/wp-cli-tests - scanFiles: - - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php - - utils/phpstan/scan-files.php - - php/boot-fs.php - treatPhpDocTypesAsCertain: false - dynamicConstantNames: - - WP_CLI_ROOT - - WP_DEBUG - - WP_DEBUG_LOG - - WP_DEBUG_DISPLAY - - WP_CLI_VERSION - strictRules: - uselessCast: true - closureUsesThis: true - overwriteVariablesWithLoop: true - matchingInheritedMethodNames: true - numericOperandsInArithmeticOperators: true - switchConditionsMatchingType: true - ignoreErrors: - - identifier: missingType.iterableValue - - identifier: missingType.property - - identifier: missingType.parameter - - identifier: missingType.return - - identifier: function.deprecated - path: tests diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 316b7b139..529dae0ef 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,26 +1,11 @@ - + - - tests + + tests/ + tests/ - - - - php - - diff --git a/schemas/wp-cli-config.json b/schemas/wp-cli-config.json deleted file mode 100644 index d3cad4564..000000000 --- a/schemas/wp-cli-config.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WP-CLI Configuration Schema", - "description": "JSON Schema for validating wp-cli.yml configuration files", - "type": "object", - "properties": { - "$schema": { - "type": "string", - "description": "JSON Schema reference" - }, - "path": { - "type": "string", - "description": "Path to the WordPress files.", - "default": null - }, - "ssh": { - "type": "string", - "description": "Perform operation against a remote server over SSH (or a container using scheme of \"docker\", \"docker-compose\", \"docker-compose-run\", \"vagrant\").", - "default": null - }, - "http": { - "type": "string", - "format": "uri", - "description": "Perform operation against a remote WordPress installation over HTTP.", - "default": null - }, - "url": { - "type": "string", - "format": "uri", - "description": "Pretend request came from given URL. In multisite, this argument is how the target site is specified.", - "default": null - }, - "user": { - "type": "string", - "description": "Set the WordPress user.", - "default": null - }, - "skip-plugins": { - "type": "array", - "items": { - "type": "string", - "description": "Plugin slug" - }, - "description": "Skip loading all or some plugins. Note: mu-plugins are still loaded.", - "default": [] - }, - "skip-themes": { - "type": "array", - "items": { - "type": "string", - "description": "Theme slug" - }, - "description": "Skip loading all or some themes.", - "default": [] - }, - "skip-packages": { - "type": "boolean", - "description": "Skip loading all installed packages.", - "default": false - }, - "require": { - "type": "array", - "items": { - "type": "string", - "description": "File path" - }, - "description": "Load PHP file before running the command (may be used more than once).", - "default": [] - }, - "exec": { - "type": "array", - "items": { - "type": "string", - "description": "PHP code to execute" - }, - "description": "Execute PHP code before running the command (may be used more than once).", - "default": [] - }, - "context": { - "type": "string", - "description": "Load WordPress in a given context.", - "oneOf": [ - { "const": "admin", "description": "A context that simulates running a command as if it would be executed in the administration backend." }, - { "const": "auto", "description": "Switches between 'cli' and 'admin' depending on which command is being used." }, - { "const": "cli", "description": "This is something in-between a frontend and an admin request, to get around some of the quirks of WordPress when running on the console." }, - { "const": "frontend", "description": "This does nothing yet." } - ], - "default": "auto" - }, - "disabled_commands": { - "type": "array", - "items": { - "type": "string", - "description": "Command" - }, - "description": "(Sub)commands to disable.", - "default": [] - }, - "color": { - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "const": "auto" - } - ], - "description": "Whether to colorize the output.", - "default": "auto" - }, - "debug": { - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string" - } - ], - "description": "Show all PHP errors; add verbosity to WP-CLI bootstrap.", - "default": false - }, - "quiet": { - "type": "boolean", - "description": "Suppress informational messages.", - "default": false - }, - "apache_modules": { - "type": "array", - "items": { - "type": "string", - "description": "Module" - }, - "description": "List of Apache Modules that are to be reported as loaded.", - "default": [] - }, - "env": { - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ] - }, - "description": "Set environment variable values. These will be used as fallbacks when the corresponding environment variable is not set in the shell. All values are converted to strings.", - "default": {} - }, - "_": { - "type": "object", - "properties": { - "merge": { - "type": "boolean", - "description": "Merge subcommand defaults from the upstream config.yml, instead of overriding", - "default": false - }, - "inherit": { - "type": "string", - "description": "Inherit configuration from an arbitrary YAML file", - "default": null - } - }, - "description": "'_' is a special value denoting configuration options for this wp-cli.yml" - } - }, - "patternProperties": { - "^@[_a-zA-Z][_a-zA-Z0-9]*$": { - "oneOf": [ - { - "type": "object", - "properties": { - "user": { - "type": "string", - "description": "Set the WordPress user." - }, - "url": { - "type": "string", - "description": "Pretend request came from given URL. In multisite, this argument is how the target site is specified." - }, - "path": { - "type": "string", - "description": "Path to the WordPress files." - }, - "ssh": { - "type": "string", - "description": "Perform operation against a remote server over SSH (or a container using scheme of \"docker\", \"docker-compose\", \"docker-compose-run\", \"vagrant\")." - }, - "http": { - "type": "string", - "format": "uri", - "description": "Perform operation against a remote WordPress installation over HTTP." - } - }, - "description": "An alias can include 'user', 'url', 'path', 'ssh', or 'http'" - }, - { - "type": "array", - "items": { - "type": "string", - "description": "Alias name" - }, - "description": "Aliases can reference other aliases to create alias groups. Alias groups can be nested" - } - ], - "description": "Aliases to other WordPress installs (e.g. `wp @staging rewrite flush`)" - }, - "^[a-z]+[a-z-]*\\s[a-z-]+.*$": { - "type": "object", - "additionalProperties": true, - "description": "Subcommand defaults (e.g. `wp config create`)" - } - }, - "additionalProperties": false -} diff --git a/schemas/wp-cli.example.yml b/schemas/wp-cli.example.yml deleted file mode 100644 index 98b89df64..000000000 --- a/schemas/wp-cli.example.yml +++ /dev/null @@ -1,55 +0,0 @@ -$schema: "./wp-cli-config.json" -path: wp-core -url: http://example.com -user: admin -color: false -disabled_commands: - - db drop - - plugin install -require: - - path-to/command.php - -# Environment variables configuration -# Values defined here will be used as fallbacks when the corresponding -# environment variable is not set in the shell. -# Note: String values that look like YAML booleans (yes/no/true/false) must be quoted. -env: - WP_CLI_CACHE_DIR: /tmp/wp-cli-cache - WP_CLI_PACKAGES_DIR: /tmp/wp-cli-packages - WP_CLI_CACHE_EXPIRY: 3600 - WP_CLI_CACHE_MAX_SIZE: 104857600 - WP_CLI_ERROR_RERUN: "prompt" - WP_CLI_AUTO_UPDATE_PROMPT: "no" - WP_CLI_DISABLE_AUTO_CHECK_UPDATE: "1" - WP_CLI_AUTO_CHECK_UPDATE_DAYS: 7 - WP_CLI_AUTOCORRECT: "1" - -config create: - dbuser: root - dbpass: - extra-php: | - define( 'WP_DEBUG', true ); - define( 'WP_POST_REVISIONS', 50 ); - -"@foo": - bar: "baz" - -"@staging": - ssh: wpcli@staging.wp-cli.org - user: wpcli - path: /srv/www/staging.wp-cli.org - -"@production": - ssh: wpcli@wp-cli.org:2222 - user: wpcli - path: /srv/www/wp-cli.org - -# Aliases can reference other aliases to create alias groups -# Alias groups can be nested -"@both": - - "@staging" - - "@production" - -_: - merge: true - inherit: prod.yml diff --git a/templates/man-params.mustache b/templates/man-params.mustache index 8a8b6193a..dcf83d419 100644 --- a/templates/man-params.mustache +++ b/templates/man-params.mustache @@ -6,7 +6,6 @@ {{/has_subcommands}} -{{#has_parameters}} ## GLOBAL PARAMETERS {{#parameters}} @@ -14,4 +13,7 @@ {{desc}} {{/parameters}} -{{/has_parameters}} +{{#root_command}} + Run 'wp help ' to get more information on a specific command. + +{{/root_command}} diff --git a/templates/man.mustache b/templates/man.mustache index 55ed2dcbc..fbb2e9bd6 100644 --- a/templates/man.mustache +++ b/templates/man.mustache @@ -6,14 +6,6 @@ ## DESCRIPTION {{shortdesc}} -{{#root_command}} - - Run 'wp help ' to get more information on a specific command. - - See the handbook for more information on WP-CLI: - https://make.wordpress.org/cli/handbook/ - -{{/root_command}} {{/shortdesc}} ## SYNOPSIS diff --git a/templates/plugin-status.mustache b/templates/plugin-status.mustache new file mode 100644 index 000000000..375862690 --- /dev/null +++ b/templates/plugin-status.mustache @@ -0,0 +1,6 @@ +Plugin %9{{slug}}%n details: + Name: {{name}} + Status: {{status}}%n + Version: {{version}} + Author: {{author}} + Description: {{description}} diff --git a/templates/theme-status.mustache b/templates/theme-status.mustache new file mode 100644 index 000000000..c53be6338 --- /dev/null +++ b/templates/theme-status.mustache @@ -0,0 +1,5 @@ +Theme %9{{slug}}%n details: + Name: {{name}} + Status: {{status}}%n + Version: {{version}} + Author: {{author}} diff --git a/templates/versions.mustache b/templates/versions.mustache new file mode 100644 index 000000000..a52954e82 --- /dev/null +++ b/templates/versions.mustache @@ -0,0 +1,4 @@ +WordPress version: {{wp-version}} +Database revision: {{db-version}} +TinyMCE version: {{mce-version}} +Package language: {{local-package}} diff --git a/tests/ArgAliasTest.php b/tests/ArgAliasTest.php deleted file mode 100644 index 05eb418a0..000000000 --- a/tests/ArgAliasTest.php +++ /dev/null @@ -1,111 +0,0 @@ -assertCount( 1, $params ); - $param = $params[0]; - $this->assertEquals( 'flag', $param['type'] ); - $this->assertEquals( 'with-dependencies', $param['name'] ); - $this->assertEquals( [ 'w' ], $param['aliases'] ); - $this->assertTrue( $param['optional'] ); - } - - public function test_synopsis_parser_extracts_multiple_aliases(): void { - $params = SynopsisParser::parse( '[--verbose|v|wordy|deprecated-name]' ); - - $this->assertCount( 1, $params ); - $param = $params[0]; - $this->assertEquals( 'flag', $param['type'] ); - $this->assertEquals( 'verbose', $param['name'] ); - $this->assertEquals( [ 'v', 'wordy', 'deprecated-name' ], $param['aliases'] ); - } - - public function test_synopsis_parser_extracts_aliases_from_assoc_param(): void { - $params = SynopsisParser::parse( '[--number=|n]' ); - - $this->assertCount( 1, $params ); - $param = $params[0]; - $this->assertEquals( 'assoc', $param['type'] ); - $this->assertEquals( 'number', $param['name'] ); - $this->assertEquals( [ 'n' ], $param['aliases'] ); - $this->assertTrue( $param['optional'] ); - } - - public function test_synopsis_parser_no_aliases_when_absent(): void { - $params = SynopsisParser::parse( '[--verbose]' ); - - $this->assertCount( 1, $params ); - $param = $params[0]; - $this->assertEquals( 'flag', $param['type'] ); - $this->assertEquals( 'verbose', $param['name'] ); - $this->assertArrayNotHasKey( 'aliases', $param ); - } - - public function test_synopsis_parser_ignores_pipe_inside_value_brackets(): void { - // The | inside should NOT be treated as an alias separator - $params = SynopsisParser::parse( '' ); - - $this->assertCount( 1, $params ); - $param = $params[0]; - $this->assertEquals( 'positional', $param['type'] ); - $this->assertArrayNotHasKey( 'aliases', $param ); - } - - public function test_synopsis_parser_assoc_alias_with_pipe_in_value(): void { - // Pipe inside <...> is ignored; alias is only extracted from outside - $params = SynopsisParser::parse( '[--type=|t]' ); - - $this->assertCount( 1, $params ); - $param = $params[0]; - $this->assertEquals( 'assoc', $param['type'] ); - $this->assertEquals( 'type', $param['name'] ); - $this->assertEquals( [ 't' ], $param['aliases'] ); - } - - public function test_synopsis_parser_render_includes_aliases_for_flag(): void { - $synopsis = [ - [ - 'type' => 'flag', - 'name' => 'verbose', - 'aliases' => [ 'v', 'wordy' ], - 'optional' => true, - 'repeating' => false, - ], - ]; - - $rendered = SynopsisParser::render( $synopsis ); - $this->assertEquals( '[--verbose|v|wordy]', $rendered ); - } - - public function test_synopsis_parser_render_includes_aliases_for_assoc(): void { - $synopsis = [ - [ - 'type' => 'assoc', - 'name' => 'number', - 'aliases' => [ 'n' ], - 'optional' => true, - 'repeating' => false, - 'value' => [ - 'optional' => false, - 'name' => 'number', - ], - ], - ]; - - $rendered = SynopsisParser::render( $synopsis ); - $this->assertEquals( '[--number=|n]', $rendered ); - } - - public function test_synopsis_roundtrip_with_aliases(): void { - $synopsis = '[--number=|n] [--with-dependencies|w] [--verbose|v|wordy]'; - $parsed = SynopsisParser::parse( $synopsis ); - $rendered = SynopsisParser::render( $parsed ); - $this->assertEquals( $synopsis, $rendered ); - } -} diff --git a/tests/ArgValidationTest.php b/tests/ArgValidationTest.php deleted file mode 100644 index b04ca0384..000000000 --- a/tests/ArgValidationTest.php +++ /dev/null @@ -1,74 +0,0 @@ - []' ); - - $this->assertFalse( $validator->enough_positionals( [] ) ); - $this->assertTrue( $validator->enough_positionals( [ 1, 2 ] ) ); - $this->assertTrue( $validator->enough_positionals( [ 1, 2, 3, 4 ] ) ); - - $this->assertEquals( [ 4 ], $validator->unknown_positionals( [ 1, 2, 3, 4 ] ) ); - } - - public function testRepeatingPositional(): void { - $validator = new SynopsisValidator( ' [...]' ); - - $this->assertFalse( $validator->enough_positionals( [] ) ); - $this->assertTrue( $validator->enough_positionals( [ 1 ] ) ); - $this->assertTrue( $validator->enough_positionals( [ 1, 2, 3 ] ) ); - - $this->assertEmpty( $validator->unknown_positionals( [ 1, 2, 3 ] ) ); - } - - public function testUnknownAssocEmpty(): void { - $validator = new SynopsisValidator( '' ); - - $assoc_args = [ - 'foo' => true, - 'bar' => false, - ]; - $this->assertEquals( array_keys( $assoc_args ), $validator->unknown_assoc( $assoc_args ) ); - } - - public function testUnknownAssoc(): void { - $validator = new SynopsisValidator( '--type= [--brand=] [--flag]' ); - - $assoc_args = [ - 'type' => 'analog', - 'brand' => true, - 'flag' => true, - ]; - $this->assertEmpty( $validator->unknown_assoc( $assoc_args ) ); - - $assoc_args['another'] = true; - $this->assertContains( 'another', $validator->unknown_assoc( $assoc_args ) ); - } - - public function testMissingAssoc(): void { - $validator = new SynopsisValidator( '--type= [--brand=] [--flag]' ); - - $assoc_args = [ - 'brand' => true, - 'flag' => true, - ]; - list( $errors, $to_unset ) = $validator->validate_assoc( $assoc_args ); - - $this->assertCount( 1, $errors['fatal'] ); - $this->assertCount( 1, $errors['warning'] ); - } - - public function testAssocWithOptionalValue(): void { - $validator = new SynopsisValidator( '[--network[=]]' ); - - $assoc_args = [ 'network' => true ]; - list( $errors, $to_unset ) = $validator->validate_assoc( $assoc_args ); - - $this->assertCount( 0, $errors['fatal'] ); - $this->assertCount( 0, $errors['warning'] ); - } -} diff --git a/tests/CLICommandTest.php b/tests/CLICommandTest.php deleted file mode 100644 index 72b8090fc..000000000 --- a/tests/CLICommandTest.php +++ /dev/null @@ -1,193 +0,0 @@ -setAccessible( true ); - } - $this->prev_capture_exit = $class_wp_cli_capture_exit->getValue(); - $class_wp_cli_capture_exit->setValue( null, true ); - - $this->prev_logger = WP_CLI::get_logger(); - $this->logger = new WP_CLI\Loggers\Execution(); - WP_CLI::set_logger( $this->logger ); - } - - public function tearDown(): void { - // Restore state. - $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $class_wp_cli_capture_exit->setAccessible( true ); - } - $class_wp_cli_capture_exit->setValue( null, $this->prev_capture_exit ); - - WP_CLI::set_logger( $this->prev_logger ); - - parent::tearDown(); - } - - private function call_replace_current_phar( $temp, $current_phar ) { - $cli_command = new CLI_Command(); - $method = new \ReflectionMethod( $cli_command, 'replace_current_phar' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - $method->invoke( $cli_command, $temp, $current_phar ); - } - - public function testReplaceCurrentPharNonWindowsSuccess(): void { - if ( WP_CLI\Utils\is_windows() ) { - $this->markTestSkipped( 'Not applicable on Windows' ); - } - - $temp = tempnam( sys_get_temp_dir(), 'wp-cli-temp-' ); - $current_phar = tempnam( sys_get_temp_dir(), 'wp-cli-current-' ); - - file_put_contents( $temp, 'new content' ); - file_put_contents( $current_phar, 'old content' ); - - $this->call_replace_current_phar( $temp, $current_phar ); - - $this->assertFileExists( $current_phar ); - $this->assertSame( 'new content', file_get_contents( $current_phar ) ); - $this->assertFileDoesNotExist( $temp ); - - @unlink( $current_phar ); - } - - public function testReplaceCurrentPharNonWindowsFailure(): void { - if ( WP_CLI\Utils\is_windows() ) { - $this->markTestSkipped( 'Not applicable on Windows' ); - } - - $temp = tempnam( sys_get_temp_dir(), 'wp-cli-temp-' ); - $current_phar = '/nonexistent/dir/wp-cli.phar'; // Invalid path to trigger rename failure. - - file_put_contents( $temp, 'new content' ); - - $this->expectException( ExitException::class ); - - try { - $this->call_replace_current_phar( $temp, $current_phar ); - } finally { - $this->assertFileDoesNotExist( $temp ); // Verify cleanup. - $this->assertStringContainsString( 'Cannot move', $this->logger->stderr ); - } - } - - public function testReplaceCurrentPharWindowsSuccess(): void { - if ( ! WP_CLI\Utils\is_windows() ) { - $this->markTestSkipped( 'Windows only test' ); - } - - $temp = tempnam( sys_get_temp_dir(), 'wp-cli-temp-' ); - $current_phar = tempnam( sys_get_temp_dir(), 'wp-cli-current-' ); - $bak_file = $current_phar . '.bak'; - - file_put_contents( $temp, 'new content' ); - file_put_contents( $current_phar, 'old content' ); - file_put_contents( $bak_file, 'stale backup' ); - - $this->call_replace_current_phar( $temp, $current_phar ); - - $this->assertFileExists( $current_phar ); - $this->assertSame( 'new content', file_get_contents( $current_phar ) ); - $this->assertFileDoesNotExist( $temp ); - $this->assertFileDoesNotExist( $bak_file ); - - @unlink( $current_phar ); - } - - public function testReplaceCurrentPharWindowsStaleBackupDeletionFailure(): void { - if ( ! WP_CLI\Utils\is_windows() ) { - $this->markTestSkipped( 'Windows only test' ); - } - - $temp = tempnam( sys_get_temp_dir(), 'wp-cli-temp-' ); - $current_phar = tempnam( sys_get_temp_dir(), 'wp-cli-current-' ); - $bak_file = $current_phar . '.bak'; - - file_put_contents( $temp, 'new content' ); - file_put_contents( $current_phar, 'old content' ); - mkdir( $bak_file ); // Make it a directory to cause unlink failure. - - $this->expectException( ExitException::class ); - - try { - $this->call_replace_current_phar( $temp, $current_phar ); - } finally { - $this->assertFileDoesNotExist( $temp ); // Verify cleanup. - $this->assertFileExists( $bak_file ); // Stale backup is still there because unlink failed. - $this->assertStringContainsString( 'Cannot remove existing backup', $this->logger->stderr ); - - rmdir( $bak_file ); - @unlink( $current_phar ); - } - } - - public function testReplaceCurrentPharWindowsRenameToBackupFailure(): void { - if ( ! WP_CLI\Utils\is_windows() ) { - $this->markTestSkipped( 'Windows only test' ); - } - - $temp = tempnam( sys_get_temp_dir(), 'wp-cli-temp-' ); - $current_phar = '/nonexistent/dir/wp-cli.phar'; // Invalid path to trigger backup failure. - - file_put_contents( $temp, 'new content' ); - - $this->expectException( ExitException::class ); - - try { - $this->call_replace_current_phar( $temp, $current_phar ); - } finally { - $this->assertFileDoesNotExist( $temp ); // Verify cleanup. - $this->assertStringContainsString( 'Cannot rename', $this->logger->stderr ); - } - } - - public function testReplaceCurrentPharWindowsMoveFailureReverts(): void { - if ( ! WP_CLI\Utils\is_windows() ) { - $this->markTestSkipped( 'Windows only test' ); - } - - $temp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'wp-cli-nonexistent-temp-' . uniqid(); - $current_phar = tempnam( sys_get_temp_dir(), 'wp-cli-current-' ); - $bak_file = $current_phar . '.bak'; - - file_put_contents( $current_phar, 'old content' ); - - $this->expectException( ExitException::class ); - - try { - $this->call_replace_current_phar( $temp, $current_phar ); - } finally { - $this->assertFileDoesNotExist( $temp ); - $this->assertFileExists( $current_phar ); // Reverted backup back to original. - $this->assertSame( 'old content', file_get_contents( $current_phar ) ); - $this->assertFileDoesNotExist( $bak_file ); // Bak file is gone. - $this->assertStringContainsString( 'Cannot move', $this->logger->stderr ); - $this->assertStringContainsString( 'The original Phar was successfully restored.', $this->logger->stderr ); - - @unlink( $current_phar ); - } - } -} diff --git a/tests/ConfiguratorTest.php b/tests/ConfiguratorTest.php deleted file mode 100644 index 2fff55368..000000000 --- a/tests/ConfiguratorTest.php +++ /dev/null @@ -1,199 +0,0 @@ -assertCount( 1, $args[0] ); - $this->assertCount( 2, $args[1] ); - - $this->assertEquals( 'foo', $args[0][0] ); - - $this->assertEquals( 'bar', $args[1][0][0] ); - $this->assertTrue( $args[1][0][1] ); - - $this->assertEquals( 'baz', $args[1][1][0] ); - $this->assertEquals( 'text', $args[1][1][1] ); - } - - public function testExtractAssocNoValue(): void { - $args = Configurator::extract_assoc( [ 'foo', '--bar=', '--baz=text' ] ); - - $this->assertCount( 1, $args[0] ); - $this->assertCount( 2, $args[1] ); - - $this->assertEquals( 'foo', $args[0][0] ); - - $this->assertEquals( 'bar', $args[1][0][0] ); - $this->assertEmpty( $args[1][0][1] ); - - $this->assertEquals( 'baz', $args[1][1][0] ); - $this->assertEquals( 'text', $args[1][1][1] ); - } - - public function testExtractAssocGlobalLocal(): void { - $args = Configurator::extract_assoc( [ '--url=foo.dev', '--path=wp', 'foo', '--bar=', '--baz=text', '--url=bar.dev' ] ); - - $this->assertCount( 1, $args[0] ); - $this->assertCount( 5, $args[1] ); - $this->assertCount( 2, $args[2] ); - $this->assertCount( 3, $args[3] ); - - $this->assertEquals( 'url', $args[2][0][0] ); - $this->assertEquals( 'foo.dev', $args[2][0][1] ); - $this->assertEquals( 'url', $args[3][2][0] ); - $this->assertEquals( 'bar.dev', $args[3][2][1] ); - } - - public function testExtractAssocDoubleDashInValue(): void { - $args = Configurator::extract_assoc( [ '--test=text--text' ] ); - - $this->assertCount( 0, $args[0] ); - $this->assertCount( 1, $args[1] ); - - $this->assertEquals( 'test', $args[1][0][0] ); - $this->assertEquals( 'text--text', $args[1][0][1] ); - } - - public function testExtractAssocDoubleDashDelimiter(): void { - // Arguments after `--` should be treated as positional. - $args = Configurator::extract_assoc( [ 'foo', '--bar', '--', '--baz=text' ] ); - - $this->assertCount( 2, $args[0] ); - $this->assertCount( 1, $args[1] ); - - $this->assertEquals( 'foo', $args[0][0] ); - $this->assertEquals( '--baz=text', $args[0][1] ); - - $this->assertEquals( 'bar', $args[1][0][0] ); - $this->assertTrue( $args[1][0][1] ); - } - - public function testExtractAssocDoubleDashDelimiterWithGlobalAssoc(): void { - // Global assoc args before `--` should still be captured. - $args = Configurator::extract_assoc( [ '--url=foo.dev', 'command', '--', '--require=/blah' ] ); - - $this->assertCount( 2, $args[0] ); - $this->assertCount( 1, $args[1] ); - $this->assertCount( 1, $args[2] ); - $this->assertCount( 0, $args[3] ); - - $this->assertEquals( 'command', $args[0][0] ); - $this->assertEquals( '--require=/blah', $args[0][1] ); - - $this->assertEquals( 'url', $args[2][0][0] ); - $this->assertEquals( 'foo.dev', $args[2][0][1] ); - } - - public function testExtractAssocDoubleDashDelimiterAtStart(): void { - // `--` at the beginning should make all following args positional. - $args = Configurator::extract_assoc( [ '--', 'command', '--option=value' ] ); - - $this->assertCount( 2, $args[0] ); - $this->assertCount( 0, $args[1] ); - $this->assertCount( 0, $args[2] ); - $this->assertCount( 0, $args[3] ); - - $this->assertEquals( 'command', $args[0][0] ); - $this->assertEquals( '--option=value', $args[0][1] ); - } - - public function testExtractAssocDoubleDashDelimiterMultipleArgs(): void { - // Multiple option-like arguments after `--` should all be positional. - $args = Configurator::extract_assoc( [ 'option', 'get', 'home', '--', '--require=/blah', '--no-color' ] ); - - $this->assertCount( 5, $args[0] ); - $this->assertCount( 0, $args[1] ); - - $this->assertEquals( 'option', $args[0][0] ); - $this->assertEquals( 'get', $args[0][1] ); - $this->assertEquals( 'home', $args[0][2] ); - $this->assertEquals( '--require=/blah', $args[0][3] ); - $this->assertEquals( '--no-color', $args[0][4] ); - } - - /** - * WP_CLI::get_config does not show warnings for null values. - */ - public function testNullGetConfig(): void { - // Init config so there is a config to check. - $runner = WP_CLI::get_runner(); - $runner->init_config(); - - // Previous. - $prev_logger = WP_CLI::get_logger(); - - $logger = new Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - $has_config = WP_CLI::has_config( 'url' ); - $get_config = WP_CLI::get_config( 'url' ); - - $this->assertTrue( $has_config, 'has_config() is not true' ); - $this->assertTrue( false === strpos( $logger->stderr, 'Warning' ), 'Logger contains a "Warning"' ); - $this->assertNull( $get_config, 'get_config() is not null' ); - - // Restore. - WP_CLI::set_logger( $prev_logger ); - } - - public function testExtractAssocMultipleValues(): void { - $args = Configurator::extract_assoc( [ 'list', '--status=active', '--status=parent' ] ); - - $this->assertCount( 1, $args[0] ); - $this->assertCount( 2, $args[1] ); - - $this->assertEquals( 'list', $args[0][0] ); - - $this->assertEquals( 'status', $args[1][0][0] ); - $this->assertEquals( 'active', $args[1][0][1] ); - - $this->assertEquals( 'status', $args[1][1][0] ); - $this->assertEquals( 'parent', $args[1][1][1] ); - } - - public function testParseArgsAggregatesMultipleValues(): void { - $argv = [ 'list', '--status=active', '--status=parent', '--field=name' ]; - - $configurator = new Configurator( __DIR__ . '/../php/config-spec.php' ); - $parsed = $configurator->parse_args( $argv ); - - // Positional arguments should remain unchanged. - $this->assertSame( [ 'list' ], $parsed[0] ); - - // Repeated flags should be aggregated into an array. - $this->assertArrayHasKey( 'status', $parsed[1] ); - $this->assertSame( [ 'active', 'parent' ], $parsed[1]['status'] ); - - // Non-repeating parameters should collapse to their last value. - $this->assertArrayHasKey( 'field', $parsed[1] ); - $this->assertSame( 'name', $parsed[1]['field'] ); - } - - public function testParseArgsBooleanFlagsUseLastWins(): void { - $argv = [ 'command', '--verbose', '--no-verbose', '--verbose' ]; - $argv2 = [ 'command', '--verbose', '--verbose', '--no-verbose' ]; - - $configurator = new Configurator( __DIR__ . '/../php/config-spec.php' ); - $parsed = $configurator->parse_args( $argv ); - - // Positional arguments should remain unchanged. - $this->assertSame( [ 'command' ], $parsed[0] ); - - // The last --verbose should win, so verbose should be true. - $this->assertArrayHasKey( 'verbose', $parsed[1] ); - $this->assertTrue( $parsed[1]['verbose'] ); - - $parsed2 = $configurator->parse_args( $argv2 ); - $assoc_args2 = $parsed2[1]; - - // The last --no-verbose should win, so verbose should be false. - $this->assertArrayHasKey( 'verbose', $assoc_args2 ); - $this->assertFalse( $assoc_args2['verbose'] ); - } -} diff --git a/tests/FileCacheTest.php b/tests/FileCacheTest.php deleted file mode 100644 index 69a125dd1..000000000 --- a/tests/FileCacheTest.php +++ /dev/null @@ -1,253 +0,0 @@ -assertSame( $cache_dir . '/', $cache->get_root() ); - unset( $cache ); - - $cache = new FileCache( $cache_dir . '/', $ttl, $max_size ); - $this->assertSame( $cache_dir . '/', $cache->get_root() ); - unset( $cache ); - - $cache = new FileCache( $cache_dir . '\\', $ttl, $max_size ); - $this->assertSame( $cache_dir . '/', $cache->get_root() ); - unset( $cache ); - - rmdir( $cache_dir ); - } - - public function test_ensure_dir_exists(): void { - $prev_logger = WP_CLI::get_logger(); - - $logger = new Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - $max_size = 32; - $ttl = 60; - $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); - - $cache = new FileCache( $cache_dir, $ttl, $max_size ); - $test_class = new ReflectionClass( $cache ); - $method = $test_class->getMethod( 'ensure_dir_exists' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - - // Cache directory should be created. - $result = $method->invokeArgs( $cache, [ $cache_dir . '/test1' ] ); - $this->assertTrue( $result ); - $this->assertTrue( is_dir( $cache_dir . '/test1' ) ); - - // Try to create the same directory again. it should return true. - $result = $method->invokeArgs( $cache, [ $cache_dir . '/test1' ] ); - $this->assertTrue( $result ); - - // `chmod()` doesn't work on Windows. - if ( ! Utils\is_windows() ) { - // It should be failed because permission denied. - $logger->stderr = ''; - chmod( $cache_dir . '/test1', 0000 ); - $result = $method->invokeArgs( $cache, [ $cache_dir . '/test1/error' ] ); - $expected = "/^Warning: Failed to create directory '.+': mkdir\(\): Permission denied\.$/"; - $this->assertMatchesRegularExpression( $expected, $logger->stderr ); - } - - // It should be failed because file exists. - $logger->stderr = ''; - file_put_contents( $cache_dir . '/test2', '' ); - $result = $method->invokeArgs( $cache, [ $cache_dir . '/test2' ] ); - $expected = "/^Warning: Failed to create directory '.+': mkdir\(\): File exists\.$/"; - $this->assertMatchesRegularExpression( $expected, $logger->stderr ); - - // Restore. - chmod( $cache_dir . '/test1', 0755 ); - rmdir( $cache_dir . '/test1' ); - unlink( $cache_dir . '/test2' ); - rmdir( $cache_dir ); - WP_CLI::set_logger( $prev_logger ); - } - - public function test_export(): void { - $max_size = 32; - $ttl = 60; - $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); - $target_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache-export/nonexistant-subdirectory', true ); - $target = $target_dir . '/foo'; - $key = 'foo'; - $contents = 'bar'; - $cache = new FileCache( $cache_dir, $ttl, $max_size ); - - // Assert subdirectory is created. - $cache->write( $key, $contents ); - $cache->export( $key, $target ); - $this->assertEquals( $contents, file_get_contents( $target ) ); - - // Clean up. - $cache->clear(); - unlink( $target ); - rmdir( $target_dir ); - } - - public function test_import(): void { - $max_size = 32; - $ttl = 60; - $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); - $cache = new FileCache( $cache_dir, $ttl, $max_size ); - - $tmp_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache-import', true ); - mkdir( $tmp_dir ); - - // "$group/$slug-$version.$ext"; - $key = 'plugin/my-fixture-plugin-1.0.0.zip'; - $fixture_filepath = $tmp_dir . '/my-downloaded-fixture-plugin-1.0.0.zip'; - - $zip = new ZipArchive(); - $zip->open( $fixture_filepath, ZipArchive::CREATE ); - $zip->addFile( __FILE__ ); - $zip->close(); - - $result = $cache->import( $key, $fixture_filepath ); - - // Assert file is imported. - $this->assertTrue( $result ); - $this->assertFileExists( "{$cache_dir}/{$key}" ); - - // Clean up. - $cache->clear(); - unlink( $fixture_filepath ); - rmdir( $tmp_dir ); - } - - /** - * @see https://github.com/wp-cli/wp-cli/pull/5947 - */ - public function test_import_do_not_use_cache_file_cannot_be_read(): void { - // `chmod()` doesn't work on Windows. - if ( Utils\is_windows() ) { - $this->markTestSkipped( 'chmod() does not restrict file read access on Windows.' ); - } - - $max_size = 32; - $ttl = 60; - $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); - $cache = new FileCache( $cache_dir, $ttl, $max_size ); - - $tmp_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache-import', true ); - mkdir( $tmp_dir ); - - $key = 'plugin/my-fixture-plugin-1.0.0.zip'; - $fixture_filepath = $tmp_dir . '/my-bad-permissions-fixture-plugin-1.0.0.zip'; - - $zip = new ZipArchive(); - $zip->open( $fixture_filepath, ZipArchive::CREATE ); - $zip->addFile( __FILE__ ); - $zip->close(); - - chmod( $fixture_filepath, 0000 ); - - // "Warning: copy(-.): Failed to open stream: Permission denied". - $error = null; - set_error_handler( - // @phpstan-ignore argument.type - function ( int $errno, string $errstr ) use ( &$error ) { - $error = $errstr; - } - ); - - $result = $cache->import( $key, $fixture_filepath ); - - restore_error_handler(); - - $this->assertNull( $error ); - $this->assertFalse( $result ); - - // Clean up. - $cache->clear(); - unlink( $fixture_filepath ); - rmdir( $tmp_dir ); - } - - /** - * Windows filenames cannot end in periods. - * - * @covers \WP_CLI\FileCache::validate_key - * - * @see https://github.com/wp-cli/wp-cli/pull/5947 - * @see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions - */ - public function test_validate_key_ending_in_period(): void { - $max_size = 32; - $ttl = 60; - $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); - $cache = new FileCache( $cache_dir, $ttl, $max_size ); - - $key = 'plugin/advanced-sidebar-menu-pro-9.5.7.'; - - $reflection = new ReflectionClass( $cache ); - - $method = $reflection->getMethod( 'validate_key' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - - $result = $method->invoke( $cache, $key ); - - $this->assertIsString( $result ); - $this->assertStringEndsNotWith( '.', $result ); - $this->assertSame( 'plugin/advanced-sidebar-menu-pro-9.5.7', $result ); - } - public function test_validate_key_traversal(): void { - $max_size = 32; - $ttl = 60; - $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); - $cache = new FileCache( $cache_dir, $ttl, $max_size ); - - $reflection = new ReflectionClass( $cache ); - $method = $reflection->getMethod( 'validate_key' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - - // Test traversal with '..' - $key = 'plugin/../../etc/passwd'; - $result = $method->invoke( $cache, $key ); - $this->assertSame( 'plugin/-/-/etc/passwd', $result ); - - // Test traversal with URL - $key = 'http://example.com/../../etc/passwd'; - $result = $method->invoke( $cache, $key ); - $this->assertSame( 'misc/http-example.com/-/-/etc/passwd', $result ); - - // Test traversal with '.' - $key = 'plugin/./etc/passwd'; - $result = $method->invoke( $cache, $key ); - $this->assertSame( 'plugin/-/etc/passwd', $result ); - - // Test multiple dots (should be allowed if not traversal) - $key = 'plugin/.../etc/passwd'; - $result = $method->invoke( $cache, $key ); - $this->assertSame( 'plugin/.../etc/passwd', $result ); - } -} diff --git a/tests/FormatterTest.php b/tests/FormatterTest.php deleted file mode 100644 index 928f3ec78..000000000 --- a/tests/FormatterTest.php +++ /dev/null @@ -1,320 +0,0 @@ - 'Alice', - 'age' => 30, - ], - [ - 'name' => 'Bob', - 'age' => 25, - ], - ]; - - $assoc_args = [ 'format' => 'test_format' ]; - $formatter = new Formatter( $assoc_args, [ 'name', 'age' ] ); - - ob_start(); - $formatter->display_items( $items ); - $output = ob_get_clean(); - - $this->assertTrue( $called, 'Custom format handler should be called' ); - $this->assertSame( 'CUSTOM', $output ); - - // Verify correct parameters were passed - $this->assertIsArray( $received_items, 'Handler should receive items array' ); - $this->assertCount( 2, $received_items, 'Handler should receive all items' ); - $this->assertIsArray( $received_items[0], 'First item should be an array' ); - $this->assertArrayHasKey( 'name', $received_items[0], 'Items should have name field' ); - $this->assertArrayHasKey( 'age', $received_items[0], 'Items should have age field' ); - $this->assertSame( [ 'name', 'age' ], $received_fields, 'Handler should receive fields array' ); - $this->assertInstanceOf( Formatter::class, $received_formatter, 'Handler should receive formatter instance' ); - $this->assertIsArray( $received_args, 'Handler should receive args array' ); - } - - public function test_get_available_formats() { - $formats = Formatter::get_available_formats(); - $this->assertContains( 'table', $formats ); - $this->assertContains( 'json', $formats ); - $this->assertContains( 'csv', $formats ); - $this->assertContains( 'yaml', $formats ); - $this->assertContains( 'count', $formats ); - $this->assertContains( 'ids', $formats ); - - // Add a custom format - Formatter::add_format( - 'xml', - static function () { - echo 'XML'; - } - ); - - $formats = Formatter::get_available_formats(); - $this->assertContains( 'xml', $formats ); - } - - public function test_custom_format_with_single_item() { - $output_collected = ''; - $handler = static function ( $items ) use ( &$output_collected ) { - foreach ( $items as $item ) { - foreach ( $item as $key => $value ) { - $output_collected .= "$key:$value "; - } - } - }; - - Formatter::add_format( 'test_single', $handler ); - - $item = [ - 'name' => 'Charlie', - 'age' => 35, - ]; - $assoc_args = [ 'format' => 'test_single' ]; - $formatter = new Formatter( $assoc_args, [ 'name', 'age' ] ); - - ob_start(); - $formatter->display_item( $item ); - ob_get_clean(); - - $this->assertStringContainsString( 'name:Charlie', $output_collected ); - $this->assertStringContainsString( 'age:35', $output_collected ); - } - - public function test_custom_format_field_filtering() { - $received_items = null; - $handler = function ( $items ) use ( &$received_items ) { - $received_items = $items; - }; - - Formatter::add_format( 'test_filter', $handler ); - - $items = [ - [ - 'name' => 'Test', - 'age' => 30, - 'email' => 'test@example.com', - ], - ]; - - // Only request name and age fields - $assoc_args = [ 'format' => 'test_filter' ]; - $formatter = new Formatter( $assoc_args, [ 'name', 'age' ] ); - - ob_start(); - $formatter->display_items( $items ); - ob_get_clean(); - - // Handler should only receive the requested fields - $this->assertIsArray( $received_items, 'Handler should receive items array' ); - $this->assertCount( 1, $received_items, 'Handler should receive 1 item' ); - $this->assertIsArray( $received_items[0], 'First item should be an array' ); - $this->assertArrayHasKey( 'name', $received_items[0] ); - $this->assertArrayHasKey( 'age', $received_items[0] ); - $this->assertArrayNotHasKey( 'email', $received_items[0], 'Non-requested field should be filtered out' ); - } - - public function test_custom_format_with_prefix() { - $received_items = null; - $handler = function ( $items ) use ( &$received_items ) { - $received_items = $items; - }; - - Formatter::add_format( 'test_prefix', $handler ); - - $items = [ - [ - 'post_title' => 'Test Post', - 'post_status' => 'publish', - ], - ]; - - // Request fields without prefix, but items have prefix - $assoc_args = [ 'format' => 'test_prefix' ]; - $formatter = new Formatter( $assoc_args, [ 'title', 'status' ], 'post' ); - - ob_start(); - $formatter->display_items( $items ); - ob_get_clean(); - - // Handler should receive items with resolved prefixed field names - $this->assertIsArray( $received_items, 'Handler should receive items array' ); - $this->assertCount( 1, $received_items, 'Handler should receive 1 item' ); - $this->assertIsArray( $received_items[0], 'First item should be an array' ); - // The fields should be resolved to the prefixed versions - $this->assertArrayHasKey( 'post_title', $received_items[0], 'Should have resolved post_title field' ); - $this->assertArrayHasKey( 'post_status', $received_items[0], 'Should have resolved post_status field' ); - $this->assertSame( 'Test Post', $received_items[0]['post_title'] ); - $this->assertSame( 'publish', $received_items[0]['post_status'] ); - } - - public function test_override_builtin_format() { - $called = false; - $handler = function () use ( &$called ) { - $called = true; - echo 'OVERRIDDEN'; - }; - - // Override the built-in json format - Formatter::add_format( 'json', $handler ); - - $items = [ - [ 'name' => 'Test' ], - ]; - - $assoc_args = [ 'format' => 'json' ]; - $formatter = new Formatter( $assoc_args, [ 'name' ] ); - - ob_start(); - $formatter->display_items( $items ); - $output = ob_get_clean(); - - $this->assertTrue( $called, 'Custom handler should override built-in format' ); - $this->assertSame( 'OVERRIDDEN', $output ); - } - - public function test_add_single_value_format() { - $called = false; - $received_value = null; - $handler = function ( $value ) use ( &$called, &$received_value ) { - $called = true; - $received_value = $value; - return 'CUSTOM:' . $value; - }; - - Formatter::add_single_value_format( 'test_single_format', $handler ); - - $result = Formatter::format_single_value( 'test_value', 'test_single_format' ); - - $this->assertTrue( $called, 'Single-value format handler should be called' ); - $this->assertSame( 'test_value', $received_value, 'Handler should receive the value' ); - $this->assertSame( 'CUSTOM:test_value', $result, 'Handler should return formatted value' ); - } - - public function test_format_single_value_json() { - $value = [ 'key' => 'value' ]; - $result = Formatter::format_single_value( $value, 'json' ); - $this->assertSame( '{"key":"value"}', $result ); - } - - public function test_format_single_value_yaml() { - $value = [ 'key' => 'value' ]; - $result = Formatter::format_single_value( $value, 'yaml' ); - $this->assertStringContainsString( 'key: value', $result ); - } - - public function test_format_single_value_var_export() { - $value = [ 'key' => 'value' ]; - $result = Formatter::format_single_value( $value, 'var_export' ); - $this->assertStringContainsString( "'key' => 'value'", $result ); - } - - public function test_format_single_value_fallback() { - // Test fallback for unregistered format - $value = [ 'key' => 'value' ]; - $result = Formatter::format_single_value( $value, 'unknown_format' ); - $this->assertStringContainsString( "'key' => 'value'", $result, 'Should fallback to var_export for arrays' ); - - // Test fallback for scalar values - $result = Formatter::format_single_value( 'simple_string', 'unknown_format' ); - $this->assertSame( 'simple_string', $result, 'Should return string as-is for scalars' ); - } - - public function test_single_item_unsupported_formats() { - $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $class_wp_cli_capture_exit->setAccessible( true ); - } - $prev_capture_exit = $class_wp_cli_capture_exit->getValue(); - $class_wp_cli_capture_exit->setValue( null, true ); - - $prev_logger = WP_CLI::get_logger(); - $logger = new WP_CLI\Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - $item = [ 'name' => 'Alice' ]; - - try { - $assoc_args = [ 'format' => 'ids' ]; - $formatter = new Formatter( $assoc_args, [ 'name' ] ); - $formatter->display_item( $item ); - $this->fail( 'Should have thrown ExitException' ); - } catch ( \WP_CLI\ExitException $e ) { - $this->assertStringContainsString( 'Error: Invalid format: ids', $logger->stderr ); - } finally { - $class_wp_cli_capture_exit->setValue( null, $prev_capture_exit ); - WP_CLI::set_logger( $prev_logger ); - } - } - - public function test_custom_format_options() { - $called = false; - $handler = function () use ( &$called ) { - $called = true; - }; - - Formatter::add_format( 'no_single', $handler, [ 'single_item' => false ] ); - - $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $class_wp_cli_capture_exit->setAccessible( true ); - } - $prev_capture_exit = $class_wp_cli_capture_exit->getValue(); - $class_wp_cli_capture_exit->setValue( null, true ); - - $prev_logger = WP_CLI::get_logger(); - $logger = new WP_CLI\Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - $item = [ 'name' => 'Bob' ]; - - try { - $assoc_args = [ 'format' => 'no_single' ]; - $formatter = new Formatter( $assoc_args, [ 'name' ] ); - $formatter->display_item( $item ); - $this->fail( 'Should have thrown ExitException' ); - } catch ( \WP_CLI\ExitException $e ) { - $this->assertStringContainsString( 'Error: Invalid format: no_single', $logger->stderr ); - $this->assertFalse( $called, 'Handler should not be called when single_item option is false' ); - } finally { - $class_wp_cli_capture_exit->setValue( null, $prev_capture_exit ); - WP_CLI::set_logger( $prev_logger ); - } - } - - public function test_plaintext_alias_print_value() { - $value = [ 'nested' => 'value' ]; - $result = Formatter::format_single_value( $value, 'plaintext' ); - - // Should match var_export output - $this->assertStringContainsString( "'nested' => 'value'", $result ); - } -} diff --git a/tests/HelpTest.php b/tests/HelpTest.php deleted file mode 100644 index bbb7f3b4c..000000000 --- a/tests/HelpTest.php +++ /dev/null @@ -1,175 +0,0 @@ -getMethod( 'parse_reference_links' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - - $desc = 'This is a [reference link](https://wordpress.org/). It should be displayed very nice!'; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected = <<<'EOL' -This is a [reference link][1]. It should be displayed very nice! - ---- -[1] https://wordpress.org/ -EOL; - $this->assertSame( $expected, $result ); - - $desc = 'This is a [reference link](https://wordpress.org/) and [second link](http://wp-cli.org/). It should be displayed very nice!'; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected = <<<'EOL' -This is a [reference link][1] and [second link][2]. It should be displayed very nice! - ---- -[1] https://wordpress.org/ -[2] http://wp-cli.org/ -EOL; - $this->assertSame( $expected, $result ); - - $desc = <<<'EOL' -This is a [reference link](https://wordpress.org/) and [second link](http://wp-cli.org/). -It should be displayed very nice! -EOL; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected = <<<'EOL' -This is a [reference link][1] and [second link][2]. -It should be displayed very nice! - ---- -[1] https://wordpress.org/ -[2] http://wp-cli.org/ -EOL; - - $this->assertSame( $expected, $result ); - - $desc = <<<'EOL' -This is a [reference link](https://wordpress.org/) and [second link](http://wp-cli.org/). -It should be displayed very nice! - -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected = <<<'EOL' -This is a [reference link][1] and [second link][2]. -It should be displayed very nice! - ---- -[1] https://wordpress.org/ -[2] http://wp-cli.org/ - -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - - $this->assertSame( $expected, $result ); - - $desc = <<<'EOL' -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected = <<<'EOL' -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - - $this->assertSame( $expected, $result ); - - $desc = <<<'EOL' -This is a long description. -It doesn't have any link. - -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected = <<<'EOL' -This is a long description. -It doesn't have any link. - -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - - $this->assertSame( $expected, $result ); - } finally { - putenv( false === $original_force_hyperlink ? 'FORCE_HYPERLINK' : "FORCE_HYPERLINK=$original_force_hyperlink" ); - } - } - - public function test_parse_reference_links_with_forced_hyperlinks(): void { - $original_force_hyperlink = getenv( 'FORCE_HYPERLINK' ); - putenv( 'FORCE_HYPERLINK=1' ); - - try { - $test_class = new ReflectionClass( 'Help_Command' ); - $method = $test_class->getMethod( 'parse_reference_links' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - - $desc = 'This is a [reference link](https://wordpress.org/). It should be displayed very nice!'; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected_link = "\033]8;;https://wordpress.org/\033\\reference link\033]8;;\033\\"; - $expected = "This is a {$expected_link}. It should be displayed very nice!"; - - $this->assertSame( $expected, $result ); - - $desc = <<<'EOL' -This is a [reference link](https://wordpress.org/) and [second link](http://wp-cli.org/). -It should be displayed very nice! - -## Example - -It doesn't expect to be link here like [reference link](https://wordpress.org/). -EOL; - $result = $method->invokeArgs( null, [ $desc ] ); - - $expected_link2 = "\033]8;;http://wp-cli.org/\033\\second link\033]8;;\033\\"; - $expected = <<assertSame( $expected, $result ); - } finally { - putenv( false === $original_force_hyperlink ? 'FORCE_HYPERLINK' : "FORCE_HYPERLINK=$original_force_hyperlink" ); - } - } -} diff --git a/tests/HttpRequestLoggingTest.php b/tests/HttpRequestLoggingTest.php deleted file mode 100644 index 92ccdce3c..000000000 --- a/tests/HttpRequestLoggingTest.php +++ /dev/null @@ -1,58 +0,0 @@ - 'value' ]; - $test_headers = [ 'X-Test' => 'test' ]; - - try { - Utils\http_request( - 'POST', - $test_url, - $test_data, - $test_headers, - [ - 'timeout' => 0.01, - 'halt_on_error' => false, - ] - ); - // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch - } catch ( \RuntimeException $e ) { - // Expected to fail due to short timeout - } - - $this->assertTrue( $hook_called, 'http_request_options hook should be called' ); - $this->assertEquals( 'POST', $received_method, 'Method should be passed to hook' ); - $this->assertEquals( $test_url, $received_url, 'URL should be passed to hook' ); - $this->assertEquals( $test_data, $received_data, 'Data should be passed to hook' ); - $this->assertEquals( $test_headers, $received_headers, 'Headers should be passed to hook' ); - } -} diff --git a/tests/InflectorTest.php b/tests/InflectorTest.php deleted file mode 100644 index 1fb46f33b..000000000 --- a/tests/InflectorTest.php +++ /dev/null @@ -1,40 +0,0 @@ -assertEquals( $expected, Inflector::pluralize( $singular ) ); - } - - public static function dataProviderPluralize(): array { - return [ - [ 'string', 'strings' ], // Regular. - [ 'person', 'people' ], // Irregular. - [ 'scissors', 'scissors' ], // Uncountable. - ]; - } - - /** - * @dataProvider dataProviderSingularize - */ - #[DataProvider( 'dataProviderSingularize' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testSingularize( $singular, $expected ): void { - $this->assertEquals( $expected, Inflector::singularize( $singular ) ); - } - - public static function dataProviderSingularize(): array { - return [ - [ 'strings', 'string' ], // Regular. - [ 'people', 'person' ], // Irregular. - [ 'scissors', 'scissors' ], // Uncountable. - ]; - } -} diff --git a/tests/LoggingTest.php b/tests/LoggingTest.php deleted file mode 100644 index a5629ac4e..000000000 --- a/tests/LoggingTest.php +++ /dev/null @@ -1,152 +0,0 @@ - [ - 'debug' => true, - ], - ]; - } - - protected function write( $handle, $str ) { - echo $str; - } -} - -class MockQuietLogger extends WP_CLI\Loggers\Quiet { - - protected function get_runner() { - // @phpstan-ignore return.type - return (object) [ - 'config' => [ - 'debug' => true, - ], - ]; - } -} - -class LoggingTest extends TestCase { - - public function testLogDebugRegularLogger(): void { - $message = 'This is a test message.'; - - $regular_logger = new MockRegularLogger( false ); - $this->expectOutputRegex( "/Debug: {$message} \(\d+\.*\d*s\)/" ); - $regular_logger->debug( $message ); - } - - public function testLogDebugQuietLogger(): void { - $message = 'This is a test message.'; - - $quiet_logger = new MockQuietLogger(); - $this->expectOutputString( '' ); - $quiet_logger->debug( $message ); - } - - public function testLogInfo(): void { - $message = 'This is a test message.'; - - $logger = new MockRegularLogger( false ); - - $this->expectOutputString( "$message\n$message\n" ); - $logger->info( $message ); - $logger->info( $message, true ); - } - - public function testLogInfoWithoutNewline(): void { - $message = 'This is a test message.'; - - $logger = new MockRegularLogger( false ); - - $this->expectOutputString( $message ); - $logger->info( $message, false ); - } - - public function testLogEscaping(): void { - $logger = new MockRegularLogger( false ); - - $message = 'foo%20bar'; - - $this->expectOutputString( "Success: $message\n" ); - $logger->success( $message ); - } - - public function testExecutionLogger(): void { - // Save Runner config. - $runner = WP_CLI::get_runner(); - $runner_config = new \ReflectionProperty( $runner, 'config' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $runner_config->setAccessible( true ); - } - - $prev_config = $runner_config->getValue( $runner ); - - // Set debug. - $runner_config->setValue( $runner, [ 'debug' => true ] ); - - $logger = new WP_CLI\Loggers\Execution(); - - // Standard use. - - $logger->info( 'info' ); - $logger->info( 'info2' ); - $logger->success( 'success' ); - $logger->warning( 'warning' ); - $logger->error( 'error' ); - $logger->success( 'success2' ); - $logger->warning( 'warning2' ); - $logger->debug( 'debug', 'group' ); - $logger->error_multi_line( [ 'line11', 'line12', 'line13' ] ); - $logger->error( 'error2' ); - $logger->error_multi_line( [ 'line21' ] ); - $logger->debug( 'debug2', 'group2' ); - - $this->assertSame( "info\ninfo2\nSuccess: success\nSuccess: success2\n", $logger->stdout ); - - $match_count = preg_match( - '/^' - . 'Warning: warning\nError: error\n' - . 'Warning: warning2\nDebug \(group\): debug \([0-9.]+s\)\n' - . 'Error:\nline11\nline12\nline13\n---------\n\nError: error2\n' - . 'Error:\nline21\n---------\n\nDebug \(group2\): debug2 \([0-9.]+s\)$/', - $logger->stderr - ); - $this->assertSame( 1, $match_count ); - - $logger->stdout = ''; - $logger->stderr = ''; - - // With output buffering. - - $logger->ob_start(); - - echo 'echo'; - $logger->info( 'info' ); - print "print\n"; - $logger->success( 'success' ); - echo "echo2\n"; - $logger->error( 'error' ); - echo "echo3\n"; - $logger->success( 'success2' ); - echo 'echo4'; - - $logger->ob_end(); - - $this->assertSame( "echoinfo\nprint\nSuccess: success\necho2\necho3\nSuccess: success2\necho4", $logger->stdout ); - $this->assertSame( "Error: error\n", $logger->stderr ); - - $logger->stdout = ''; - $logger->stderr = ''; - - // Restore. - $runner_config->setValue( $runner, $prev_config ); - } -} diff --git a/tests/PathTest.php b/tests/PathTest.php deleted file mode 100644 index a87d5f135..000000000 --- a/tests/PathTest.php +++ /dev/null @@ -1,175 +0,0 @@ -assertSame( - $expected, - Path::is_absolute( $path ), - "Failed asserting that path '{$path}' is recognized correctly." - ); - } - - /** - * @dataProvider dataProviderPathCases - */ - #[DataProvider( 'dataProviderPathCases' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testPathIsRecognizedAsAbsolute( $path, $expected ) { - $this->assertSame( - $expected, - Utils\is_path_absolute( $path ), - "Failed asserting that path '{$path}' is recognized correctly." - ); - } - - public static function dataProviderPathCases(): array { - return [ - // Windows-style absolute paths. - [ 'C:\\wp\\public/', true ], - [ 'C:/wp/public/', true ], - [ 'C:\\wp\\public', true ], - [ 'C:\\', true ], - [ 'c:\\', true ], - [ 'c:/path', true ], - [ 'C:\\wp/public', true ], - [ 'C:', false ], - [ '\\\\Server\\Share', true ], // UNC path. - - // Unix-style absolute paths. - [ '/var/www/html/', true ], - [ '/', true ], // Root. - - // Relative paths (not absolute). - [ './relative/path', false ], - [ '', false ], - ]; - } - - public function testGetHomeDir(): void { - $home = getenv( 'HOME' ); - $homedrive = getenv( 'HOMEDRIVE' ); - $homepath = getenv( 'HOMEPATH' ); - - putenv( 'HOME=/home/user' ); - $this->assertSame( '/home/user', Path::get_home_dir() ); - - putenv( 'HOME' ); - - putenv( 'HOMEDRIVE=D:' ); - putenv( 'HOMEPATH' ); - $this->assertSame( 'D:', Path::get_home_dir() ); - - putenv( 'HOMEPATH=\\Windows\\User\\' ); - $this->assertSame( 'D:\\Windows\\User', Path::get_home_dir() ); - - // Restore environments. - putenv( false === $home ? 'HOME' : "HOME=$home" ); - putenv( false === $homedrive ? 'HOMEDRIVE' : "HOMEDRIVE=$homedrive" ); - putenv( false === $homepath ? 'HOMEPATH' : "HOMEPATH=$homepath" ); - } - - public function testTrailingslashit(): void { - $this->assertSame( 'a/', Path::trailingslashit( 'a' ) ); - $this->assertSame( 'a/', Path::trailingslashit( 'a/' ) ); - $this->assertSame( 'a/', Path::trailingslashit( 'a\\' ) ); - $this->assertSame( 'a/', Path::trailingslashit( 'a\\//\\' ) ); - } - - public function testIsStream(): void { - $this->assertTrue( Path::is_stream( 'phar:///path/to/file.phar' ) ); - $this->assertTrue( Path::is_stream( 'php://stdin' ) ); - $this->assertTrue( Path::is_stream( 'PHAR:///path/to/file.phar' ) ); - $this->assertTrue( Path::is_stream( 'PhAr:///path/to/file.phar' ) ); - $this->assertFalse( Path::is_stream( '/www/path' ) ); - $this->assertFalse( Path::is_stream( 'C:/www/path' ) ); - $this->assertFalse( Path::is_stream( '' ) ); - $this->assertFalse( Path::is_stream( 'nonexistent_wrapper://path' ) ); - } - - /** - * @dataProvider dataNormalize - */ - #[DataProvider( 'dataNormalize' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testNormalize( $path, $expected ): void { - $this->assertEquals( $expected, Path::normalize( $path ) ); - } - - public static function dataNormalize(): array { - return [ - [ '', '' ], - // Windows paths. - [ 'C:\\www\\path\\', 'C:/www/path/' ], - [ 'C:\\www\\\\path\\', 'C:/www/path/' ], - [ 'c:/www/path', 'C:/www/path' ], - [ 'c:\\www\\path\\', 'C:/www/path/' ], - [ 'c:', 'C:' ], - [ 'c:\\', 'C:/' ], - [ 'c:\\\\www\\path\\', 'C:/www/path/' ], - [ '\\\\Domain\\DFSRoots\\share\\path\\', '//Domain/DFSRoots/share/path/' ], - [ '\\\\Server\\share\\path', '//Server/share/path' ], - [ '\\\\Server\\share', '//Server/share' ], - // Linux paths. - [ '/', '/' ], - [ '/www/path/', '/www/path/' ], - [ '/www/path/////', '/www/path/' ], - [ '/www/path', '/www/path' ], - // PHP stream wrapper paths. - [ 'phar:///path/to/file.phar/www/path', 'phar:///path/to/file.phar/www/path' ], - [ 'php://stdin', 'php://stdin' ], - [ 'phar:///path/to/file.phar/some//dir', 'phar:///path/to/file.phar/some/dir' ], - [ 'phar:///path/to/file.phar/some\\dir/file', 'phar:///path/to/file.phar/some/dir/file' ], - [ 'PHAR:///path/to/file.phar/some//dir', 'PHAR:///path/to/file.phar/some/dir' ], - [ 'PhAr:///path/to/file.phar/some\\dir/file', 'PhAr:///path/to/file.phar/some/dir/file' ], - // Paths with single-dot segments. - [ '/www/./path/', '/www/path/' ], - [ '/www/html/./public/wp/', '/www/html/public/wp/' ], - [ '/www/./path', '/www/path' ], - [ '/www/path/.', '/www/path/' ], - [ '/www/path/./', '/www/path/' ], - [ '/www/././path/', '/www/path/' ], - [ './public/wp', 'public/wp' ], - ]; - } - - public function testBasename(): void { - $this->assertSame( 'file.txt', Path::basename( '/path/to/file.txt' ) ); - $this->assertSame( 'file', Path::basename( '/path/to/file.txt', '.txt' ) ); - $this->assertSame( 'file.txt', Path::basename( 'C:\\path\\to\\file.txt' ) ); - } - - public function testExpandTilde(): void { - $home = Path::get_home_dir(); - - $this->assertEquals( $home, Path::expand_tilde( '~' ) ); - $this->assertEquals( $home . '/sites/wordpress', Path::expand_tilde( '~/sites/wordpress' ) ); - $this->assertEquals( '/absolute/path', Path::expand_tilde( '/absolute/path' ) ); - $this->assertEquals( 'relative/path', Path::expand_tilde( 'relative/path' ) ); - $this->assertEquals( '/path/to/~something', Path::expand_tilde( '/path/to/~something' ) ); - } - - public function testReplacePathConsts(): void { - $expected = "define( 'ABSPATH', dirname( 'C:\\\\Users\\\\test\'s\\\\site' ) . '/' );"; - $source = "define( 'ABSPATH', dirname( __FILE__ ) . '/' );"; - $actual = Path::replace_path_consts( $source, "C:\Users\\test's\site" ); - $this->assertSame( $expected, $actual ); - } - - public function testInsidePhar(): void { - $this->assertFalse( Path::inside_phar( '/regular/path/to/file.php' ) ); - $this->assertTrue( Path::inside_phar( 'phar:///path/to/archive.phar/file.php' ) ); - } -} diff --git a/tests/ProcessTest.php b/tests/ProcessTest.php deleted file mode 100644 index 3ceaa2e97..000000000 --- a/tests/ProcessTest.php +++ /dev/null @@ -1,32 +0,0 @@ -run(); - - $this->assertSame( $process_run->stdout, $expected_out ); - } - - public static function data_process_env(): array { - return [ - [ '', [], [], '' ], - [ 'ENV=blah', [], [ 'ENV' ], 'blah' ], - [ 'ENV="blah blah"', [], [ 'ENV' ], 'blah blah' ], - [ 'ENV_1="blah1 blah1" ENV_2="blah2" ENV_3=blah3', [ 'ENV' => 'in' ], [ 'ENV', 'ENV_1', 'ENV_2', 'ENV_3' ], 'inblah1 blah1blah2blah3' ], - [ 'ENV=blah', [ 'ENV_1' => 'in1', 'ENV_2' => 'in2' ], [ 'ENV_1', 'ENV_2', 'ENV' ], 'in1in2blah' ], - ]; - } -} diff --git a/tests/SchemaValidationTest.php b/tests/SchemaValidationTest.php deleted file mode 100644 index 4b1355df8..000000000 --- a/tests/SchemaValidationTest.php +++ /dev/null @@ -1,80 +0,0 @@ -assertNotFalse( $schema_content, 'Schema file should be readable' ); - - $schema = json_decode( $schema_content ); - $this->assertNotNull( $schema, 'Schema should be valid JSON' ); - - // Find all .yml files in the schemas directory - $yaml_files = glob( $schemas_dir . '/*.yml' ); - $this->assertNotFalse( $yaml_files, 'Should be able to glob for YAML files' ); - - foreach ( $yaml_files as $yaml_file ) { - // Load and parse the YAML file - $yaml_content = file_get_contents( $yaml_file ); - $this->assertNotFalse( $yaml_content, 'YAML file should be readable: ' . basename( $yaml_file ) ); - - $yaml_data = \Mustangostang\Spyc::YAMLLoadString( $yaml_content ); - $this->assertIsArray( $yaml_data, 'YAML should parse to an array/object: ' . basename( $yaml_file ) ); - - // Convert YAML data to object for validation - $json_string = json_encode( $yaml_data ); - $this->assertNotFalse( $json_string, 'YAML data should convert to JSON string: ' . basename( $yaml_file ) ); - - $data = json_decode( $json_string ); - $this->assertNotNull( $data, 'YAML data should convert to JSON object: ' . basename( $yaml_file ) ); - - // Validate using JSON Schema validator - $validator = new Validator(); - $validator->validate( $data, $schema ); - - $this->assertTrue( - $validator->isValid(), - $this->formatValidationErrors( basename( $yaml_file ), $validator->getErrors() ) - ); - } - } - - /** - * Format validation errors into a readable message. - * - * @param string $filename The YAML filename being validated. - * @param array $errors Array of validation errors from JsonSchema\Validator. - * @return string Formatted error message. - */ - private function formatValidationErrors( string $filename, array $errors ): string { - if ( empty( $errors ) ) { - return "YAML file {$filename} should validate against schema."; - } - - $message = "YAML file {$filename} failed schema validation:\n"; - - foreach ( $errors as $error ) { - $property = isset( $error['property'] ) ? $error['property'] : 'unknown'; - $pointer = isset( $error['pointer'] ) ? $error['pointer'] : ''; - $msg = isset( $error['message'] ) ? $error['message'] : 'Unknown error'; - - $message .= sprintf( - " - Property '%s' (at %s): %s\n", - $property, - $pointer ?: 'root', - $msg - ); - } - - return rtrim( $message ); - } -} diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php deleted file mode 100644 index 6986ea107..000000000 --- a/tests/UtilsTest.php +++ /dev/null @@ -1,1323 +0,0 @@ -assertEquals( - Utils\increment_version( '1.2.3-pre', 'same' ), - '1.2.3-pre' - ); - - $this->assertEquals( - Utils\increment_version( '1.2.3-pre', 'patch' ), - '1.2.4' - ); - - $this->assertEquals( - Utils\increment_version( '1.2.3-pre', 'minor' ), - '1.3.0' - ); - - $this->assertEquals( - Utils\increment_version( '1.2.3-pre', 'major' ), - '2.0.0' - ); - - // Custom version string. - $this->assertEquals( - Utils\increment_version( '1.2.3-pre', '4.5.6-alpha1' ), - '4.5.6-alpha1' - ); - } - - public function testGetSemVer(): void { - $original_version = '0.19.1'; - $this->assertEmpty( Utils\get_named_sem_ver( '0.18.0', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '0.19.1', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( 'nonsense', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '0.18.1-beta3', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '0.19.1-dev1', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '0.19.1-beta3', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '0.19.2-dev1', $original_version ) ); // -dev suffix not accepted by SemVer. - $this->assertEquals( 'patch', Utils\get_named_sem_ver( '0.19.2-beta3', $original_version ) ); - $this->assertEquals( 'patch', Utils\get_named_sem_ver( '0.19.2', $original_version ) ); - $this->assertEquals( 'minor', Utils\get_named_sem_ver( '0.20.0', $original_version ) ); - $this->assertEquals( 'minor', Utils\get_named_sem_ver( '0.20.3', $original_version ) ); - $this->assertEquals( 'major', Utils\get_named_sem_ver( '1.0.0', $original_version ) ); - $this->assertEquals( 'major', Utils\get_named_sem_ver( '1.1.1', $original_version ) ); - } - - public function testGetSemVerWP(): void { - $original_version = '3.0'; - $this->assertEmpty( Utils\get_named_sem_ver( '2.8', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '2.9.1', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( 'nonsense', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '2.0-beta3', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '3.0-dev1', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '3.0-beta3', $original_version ) ); - $this->assertEmpty( Utils\get_named_sem_ver( '3.0.1-dev1', $original_version ) ); // -dev suffix not accepted by SemVer. - $this->assertEquals( 'patch', Utils\get_named_sem_ver( '3.0.1-beta3', $original_version ) ); - $this->assertEquals( 'patch', Utils\get_named_sem_ver( '3.0.1', $original_version ) ); - $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1-beta3', $original_version ) ); - $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1', $original_version ) ); - $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1.1', $original_version ) ); - $this->assertEquals( 'major', Utils\get_named_sem_ver( '4.0', $original_version ) ); - $this->assertEquals( 'major', Utils\get_named_sem_ver( '4.1.1', $original_version ) ); - } - - public function testParseSSHUrl(): void { - $testcase = 'foo'; - $this->assertEquals( [ 'host' => 'foo' ], Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - $testcase = 'foo.com'; - $this->assertEquals( [ 'host' => 'foo.com' ], Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - $testcase = 'foo.com:2222'; - $expected = [ - 'host' => 'foo.com', - 'port' => 2222, - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( 2222, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - $testcase = 'foo.com:2222/path/to/dir'; - $expected = [ - 'host' => 'foo.com', - 'port' => 2222, - 'path' => '/path/to/dir', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( 2222, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - $testcase = 'foo.com~/path/to/dir'; - $expected = [ - 'host' => 'foo.com', - 'path' => '~/path/to/dir', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // No host. - $testcase = '~/path/to/dir'; - $this->assertEquals( [], Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Host and path, no port, with scp notation. - $testcase = 'foo.com:~/path/to/dir'; - $expected = [ - 'host' => 'foo.com', - 'path' => '~/path/to/dir', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - $testcase = 'foo.com:2222~/path/to/dir'; - $expected = [ - 'host' => 'foo.com', - 'path' => '~/path/to/dir', - 'port' => '2222', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( '2222', Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Explicit scheme, user, host, path, no port. - $testcase = 'ssh:bar@foo.com:~/path/to/dir'; - $expected = [ - 'scheme' => 'ssh', - 'user' => 'bar', - 'host' => 'foo.com', - 'path' => '~/path/to/dir', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'ssh', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Container scheme - $testcase = 'docker:wordpress'; - $expected = [ - 'scheme' => 'docker', - 'host' => 'wordpress', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'docker', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Container scheme with user, and host. - $testcase = 'docker:bar@wordpress'; - $expected = [ - 'scheme' => 'docker', - 'user' => 'bar', - 'host' => 'wordpress', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'docker', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Container scheme with user, host, and path. - $testcase = 'docker-compose:bar@wordpress:~/path/to/dir'; - $expected = [ - 'scheme' => 'docker-compose', - 'user' => 'bar', - 'host' => 'wordpress', - 'path' => '~/path/to/dir', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'docker-compose', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Container scheme with user, host, and path. - $testcase = 'docker-compose-run:bar@wordpress:~/path/to/dir'; - $expected = [ - 'scheme' => 'docker-compose-run', - 'user' => 'bar', - 'host' => 'wordpress', - 'path' => '~/path/to/dir', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'docker-compose-run', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Vagrant scheme. - $testcase = 'vagrant:default'; - $expected = [ - 'scheme' => 'vagrant', - 'host' => 'default', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'vagrant', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( 'default', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Vagrant scheme. - $testcase = 'vagrant:/var/www/html'; - $expected = [ - 'scheme' => 'vagrant', - 'host' => '', - 'path' => '/var/www/html', - ]; - $this->assertEquals( $expected, Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( 'vagrant', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( '', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( '/var/www/html', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - - // Unsupported scheme, should not match. - $testcase = 'foo:bar'; - $this->assertEquals( [], Utils\parse_ssh_url( $testcase ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); - $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); - } - - public static function parseStrToArgvData() { - return [ - [ [], '' ], - [ [ 'option', 'get', 'home' ], 'option get home' ], - [ [ 'core', 'download', '--path=/var/www/' ], 'core download --path=/var/www/' ], - [ [ 'eval', 'echo wp_get_current_user()->user_login;' ], 'eval "echo wp_get_current_user()->user_login;"' ], - [ [ 'post', 'create', '--post_title=Hello world!' ], 'post create --post_title="Hello world!"' ], - [ [ 'post', 'create', '--post_title=Mixed "quotes are working" hopefully' ], 'post create --post_title=\'Mixed "quotes are working" hopefully\'' ], - [ [ 'post', 'create', '--post_title=Escaped "double "quotes!' ], 'post create --post_title="Escaped \"double \"quotes!"' ], - [ [ 'post', 'create', "--post_title=Escaped 'single 'quotes!" ], "post create --post_title='Escaped \'single \'quotes!'" ], - [ [ 'search-replace', '//old-domain.com', '//new-domain.com', 'specifictable', '--all-tables' ], 'search-replace "//old-domain.com" "//new-domain.com" "specifictable" --all-tables' ], - [ [ 'i18n', 'make-pot', '/home/wporgdev/co/wordpress/trunk', '/home/wporgdev/co/wp-pot/trunk/wordpress-continents-cities.pot', '--include=wp-admin/includes/continents-cities.php', '--package-name=WordPress', '--headers={"Report-Msgid-Bugs-To":"https://core.trac.wordpress.org/"}', "--file-comment=Copyright (C) 2019 by the contributors\nThis file is distributed under the same license as the WordPress package.", '--skip-js', '--skip-audit', '--ignore-domain' ], "i18n make-pot '/home/wporgdev/co/wordpress/trunk' '/home/wporgdev/co/wp-pot/trunk/wordpress-continents-cities.pot' --include=\"wp-admin/includes/continents-cities.php\" --package-name='WordPress' --headers='{\"Report-Msgid-Bugs-To\":\"https://core.trac.wordpress.org/\"}' --file-comment='Copyright (C) 2019 by the contributors\nThis file is distributed under the same license as the WordPress package.' --skip-js --skip-audit --ignore-domain" ], - ]; - } - - /** - * @dataProvider parseStrToArgvData - */ - #[DataProvider( 'parseStrToArgvData' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testParseStrToArgv( $expected, $parseable_string ): void { - $this->assertEquals( $expected, Utils\parse_str_to_argv( $parseable_string ) ); - } - - /** - * Data provider for testParseStrToArgvStripsQuotesFromAssocValues. - * - * @return array - */ - public static function parseStrToArgvStripsQuotesFromAssocValuesData() { - return [ - 'double quotes with spaces' => [ 'cli foo --bar="baz quax"', [ 'cli', 'foo', '--bar=baz quax' ] ], - 'single quotes with spaces' => [ "cli foo --bar='baz quax'", [ 'cli', 'foo', '--bar=baz quax' ] ], - 'no quotes' => [ 'cli foo --bar=baz', [ 'cli', 'foo', '--bar=baz' ] ], - 'empty double quotes' => [ 'cli foo --bar=""', [ 'cli', 'foo', '--bar=' ] ], - 'empty single quotes' => [ "cli foo --bar=''", [ 'cli', 'foo', '--bar=' ] ], - 'escaped double quotes' => [ 'cli foo --bar="baz \"quax\""', [ 'cli', 'foo', '--bar=baz "quax"' ] ], - 'escaped single quotes' => [ "cli foo --bar='baz \\'quax\\''", [ 'cli', 'foo', "--bar=baz 'quax'" ] ], - 'single char double quotes' => [ 'media import "/path/img.jpg" --post_id="1" --featured_image --porcelain', [ 'media', 'import', '/path/img.jpg', '--post_id=1', '--featured_image', '--porcelain' ] ], - 'single char single quotes' => [ "media import '/path/img.jpg' --post_id='1' --featured_image --porcelain", [ 'media', 'import', '/path/img.jpg', '--post_id=1', '--featured_image', '--porcelain' ] ], - 'single digit double quotes' => [ '--post_id="1"', [ '--post_id=1' ] ], - 'single letter double quotes' => [ '--key="a"', [ '--key=a' ] ], - ]; - } - - /** - * Test that associative arguments with quoted values are properly parsed - * when passed to WP_CLI::runcommand(). - * - * @dataProvider parseStrToArgvStripsQuotesFromAssocValuesData - * @see https://github.com/wp-cli/wp-cli/issues/5541 - */ - #[DataProvider( 'parseStrToArgvStripsQuotesFromAssocValuesData' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testParseStrToArgvStripsQuotesFromAssocValues( $input, $expected ): void { - $result = Utils\parse_str_to_argv( $input ); - $this->assertEquals( $expected, $result ); - } - - public function testAssocArgsToString(): void { - // Strip quotes for Windows compat. - $strip_quotes = function ( $str ) { - return str_replace( [ '"', "'" ], '', $str ); - }; - - $expected = " --url='foo.dev' --porcelain --apple='banana'"; - $input = [ - 'url' => 'foo.dev', - 'porcelain' => true, - 'apple' => 'banana', - ]; - $actual = Utils\assoc_args_to_str( $input ); - $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); - - $expected = " --url='foo.dev' --require='file-a.php' --require='file-b.php' --porcelain --apple='banana'"; - $input = [ - 'url' => 'foo.dev', - 'require' => [ - 'file-a.php', - 'file-b.php', - ], - 'porcelain' => true, - 'apple' => 'banana', - ]; - $actual = Utils\assoc_args_to_str( $input ); - $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); - } - - public function testAssocArgsToStringWithSensitiveArgs(): void { - // Strip quotes for Windows compat. - $strip_quotes = function ( $str ) { - return str_replace( [ '"', "'" ], '', $str ); - }; - - // Test with sensitive arguments masked - $expected = " --username='admin' --password='[REDACTED]' --api-key='[REDACTED]' --debug"; - $input = [ - 'username' => 'admin', - 'password' => 'secretpassword123', - 'api-key' => 'myapikey456', - 'debug' => true, - ]; - $sensitive_args = [ 'password', 'api-key' ]; - $actual = Utils\assoc_args_to_str( $input, $sensitive_args ); - $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); - - // Verify sensitive values are not present in output - $this->assertStringNotContainsString( 'secretpassword123', $actual ); - $this->assertStringNotContainsString( 'myapikey456', $actual ); - - // Verify non-sensitive values are present - $this->assertStringContainsString( 'admin', $actual ); - - // Test with array values in sensitive arguments - $expected = " --username='admin' --token='[REDACTED]' --token='[REDACTED]'"; - $input = [ - 'username' => 'admin', - 'token' => [ - 'token1', - 'token2', - ], - ]; - $sensitive_args = [ 'token' ]; - $actual = Utils\assoc_args_to_str( $input, $sensitive_args ); - $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); - $this->assertStringNotContainsString( 'token1', $actual ); - $this->assertStringNotContainsString( 'token2', $actual ); - } - - public function testMysqlHostToCLIArgs(): void { - // Test hostname only, with and without 'p:' modifier. - $expected = [ - 'host' => 'hostname', - ]; - $testcase = 'hostname'; - $this->assertEquals( $expected, Utils\mysql_host_to_cli_args( $testcase ) ); - - $testcase = 'p:hostname'; - $this->assertEquals( $expected, Utils\mysql_host_to_cli_args( $testcase ) ); - - // Test hostname with port number, with and without 'p:' modifier - $expected = [ - 'host' => 'hostname', - 'port' => 3306, - 'protocol' => 'tcp', - ]; - $testcase = 'hostname:3306'; - $this->assertEquals( $expected, Utils\mysql_host_to_cli_args( $testcase ) ); - - $testcase = 'p:hostname:3306'; - $this->assertEquals( $expected, Utils\mysql_host_to_cli_args( $testcase ) ); - - // Test hostname with socket path, with and without 'p:' modifier. - $expected = [ - 'host' => 'hostname', - 'socket' => '/path/to/socket', - ]; - $testcase = 'hostname:/path/to/socket'; - $this->assertEquals( $expected, Utils\mysql_host_to_cli_args( $testcase ) ); - - $testcase = 'p:hostname:/path/to/socket'; - $this->assertEquals( $expected, Utils\mysql_host_to_cli_args( $testcase ) ); - } - - public function testForceEnvOnNixSystems(): void { - $env_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); - - putenv( 'WP_CLI_TEST_IS_WINDOWS=0' ); - $this->assertSame( '/usr/bin/env cmd', Utils\force_env_on_nix_systems( 'cmd' ) ); - $this->assertSame( '/usr/bin/env cmd', Utils\force_env_on_nix_systems( '/usr/bin/env cmd' ) ); - - putenv( 'WP_CLI_TEST_IS_WINDOWS=1' ); - $this->assertSame( 'cmd', Utils\force_env_on_nix_systems( 'cmd' ) ); - $this->assertSame( 'cmd', Utils\force_env_on_nix_systems( '/usr/bin/env cmd' ) ); - - putenv( false === $env_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$env_is_windows" ); - } - - public function testGetHomeDir(): void { - - // Save environments. - $home = getenv( 'HOME' ); - $homedrive = getenv( 'HOMEDRIVE' ); - $homepath = getenv( 'HOMEPATH' ); - - putenv( 'HOME=/home/user' ); - $this->assertSame( '/home/user', Utils\get_home_dir() ); - - putenv( 'HOME' ); - - putenv( 'HOMEDRIVE=D:' ); - putenv( 'HOMEPATH' ); - $this->assertSame( 'D:', Utils\get_home_dir() ); - - putenv( 'HOMEPATH=\\Windows\\User\\' ); - $this->assertSame( 'D:\\Windows\\User', Utils\get_home_dir() ); - - putenv( 'HOMEPATH=\\Windows\\User\\HOGE\\' ); - $this->assertSame( 'D:\\Windows\\User\\HOGE', Utils\get_home_dir() ); - - // Restore environments. - putenv( false === $home ? 'HOME' : "HOME=$home" ); - putenv( false === $homedrive ? 'HOMEDRIVE' : "HOME=$homedrive" ); - putenv( false === $homepath ? 'HOMEPATH' : "HOME=$homepath" ); - } - - public function testTrailingslashit(): void { - $this->assertSame( 'a/', Utils\trailingslashit( 'a' ) ); - $this->assertSame( 'a/', Utils\trailingslashit( 'a/' ) ); - $this->assertSame( 'a/', Utils\trailingslashit( 'a\\' ) ); - $this->assertSame( 'a/', Utils\trailingslashit( 'a\\//\\' ) ); - } - - /** - * @dataProvider dataNormalizePath - */ - #[DataProvider( 'dataNormalizePath' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testNormalizePath( $path, $expected ): void { - $this->assertEquals( $expected, Utils\normalize_path( $path ) ); - } - - public static function dataNormalizePath(): array { - return [ - [ '', '' ], - // Windows paths. - [ 'C:\\www\\path\\', 'C:/www/path/' ], - [ 'C:\\www\\\\path\\', 'C:/www/path/' ], - [ 'c:/www/path', 'C:/www/path' ], - [ 'c:\\www\\path\\', 'C:/www/path/' ], // Uppercase drive letter. - [ 'c:', 'C:' ], - [ 'c:\\', 'C:/' ], - [ 'c:\\\\www\\path\\', 'C:/www/path/' ], - [ '\\\\Domain\\DFSRoots\\share\\path\\', '//Domain/DFSRoots/share/path/' ], - [ '\\\\Server\\share\\path', '//Server/share/path' ], - [ '\\\\Server\\share', '//Server/share' ], - // Linux paths. - [ '/', '/' ], - [ '/www/path/', '/www/path/' ], - [ '/www/path/////', '/www/path/' ], - [ '/www/path', '/www/path' ], - [ '/www/path', '/www/path' ], - // PHP stream wrapper paths. - [ 'phar:///path/to/file.phar/www/path', 'phar:///path/to/file.phar/www/path' ], - [ 'php://stdin', 'php://stdin' ], - [ 'phar:///path/to/file.phar/some//dir', 'phar:///path/to/file.phar/some/dir' ], - [ 'phar:///path/to/file.phar/some\\dir/file', 'phar:///path/to/file.phar/some/dir/file' ], - [ 'PHAR:///path/to/file.phar/some//dir', 'PHAR:///path/to/file.phar/some/dir' ], - [ 'PhAr:///path/to/file.phar/some\\dir/file', 'PhAr:///path/to/file.phar/some/dir/file' ], - // Paths with single-dot segments. - [ '/www/./path/', '/www/path/' ], - [ '/www/html/./public/wp/', '/www/html/public/wp/' ], - [ '/www/./path', '/www/path' ], - [ '/www/path/.', '/www/path/' ], - [ '/www/path/./', '/www/path/' ], - [ '/www/././path/', '/www/path/' ], - [ './public/wp', 'public/wp' ], - ]; - } - - public function testIsStream(): void { - $this->assertTrue( Utils\is_stream( 'phar:///path/to/file.phar' ) ); - $this->assertTrue( Utils\is_stream( 'php://stdin' ) ); - $this->assertTrue( Utils\is_stream( 'PHAR:///path/to/file.phar' ) ); - $this->assertTrue( Utils\is_stream( 'PhAr:///path/to/file.phar' ) ); - $this->assertFalse( Utils\is_stream( '/www/path' ) ); - $this->assertFalse( Utils\is_stream( 'C:/www/path' ) ); - $this->assertFalse( Utils\is_stream( '' ) ); - $this->assertFalse( Utils\is_stream( 'nonexistent_wrapper://path' ) ); - } - - public function testNormalizeEols(): void { - $this->assertSame( "\na\ra\na\n", Utils\normalize_eols( "\r\na\ra\r\na\r\n" ) ); - } - - public function testGetTempDir(): void { - $this->assertTrue( '/' === substr( Utils\get_temp_dir(), -1 ) ); - } - - public function testHttpRequestBadAddress(): void { - // Save WP_CLI state. - $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $class_wp_cli_capture_exit->setAccessible( true ); - } - $prev_capture_exit = $class_wp_cli_capture_exit->getValue(); - - $prev_logger = WP_CLI::get_logger(); - - // Enable exit exception. - $class_wp_cli_capture_exit->setValue( null, true ); - - $logger = new Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - $exception = null; - try { - Utils\http_request( 'GET', 'https://nosuchhost_asdf_asdf_asdf.com', null /*data*/, [] /*headers*/, [ 'timeout' => 0.01 ] ); - } catch ( ExitException $ex ) { - $exception = $ex; - } - $this->assertTrue( null !== $exception ); - $this->assertTrue( 1 === $exception->getCode() ); - $this->assertTrue( empty( $logger->stdout ) ); - $this->assertTrue( false === strpos( $logger->stderr, 'Warning' ) ); - $this->assertTrue( 0 === strpos( $logger->stderr, 'Error: Failed to get url' ) ); - - // Restore. - $class_wp_cli_capture_exit->setValue( null, $prev_capture_exit ); - WP_CLI::set_logger( $prev_logger ); - } - - public static function dataHttpRequestBadCAcert(): array { - return [ - 'default request' => [ - [], - RuntimeException::class, - 'cURL error 77:', - ], - 'secure request' => [ - [ 'insecure' => false ], - RuntimeException::class, - 'cURL error 77:', - ], - 'insecure request' => [ - [ 'insecure' => true ], - false, - 'Warning: Re-trying without verify after failing to get verified url', - ], - ]; - } - - /** - * @dataProvider dataHttpRequestBadCAcert - * - * @param array $additional_options Associative array of additional options to pass to http_request(). - * @param class-string<\Throwable> $exception Class of the exception to expect. - * @param string $exception_message Message of the exception to expect. - */ - #[DataProvider( 'dataHttpRequestBadCAcert' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testHttpRequestBadCAcert( $additional_options, $exception, $exception_message ): void { - if ( ! extension_loaded( 'curl' ) ) { - $this->markTestSkipped( 'curl not available' ); - } - - // Save WP_CLI state. - $prev_logger = WP_CLI::get_logger(); - - // Create temporary file to use as a bad certificate file. - $bad_cacert_path = tempnam( sys_get_temp_dir(), 'wp-cli-badcacert-pem-' ); - file_put_contents( $bad_cacert_path, "-----BEGIN CERTIFICATE-----\nasdfasdf\n-----END CERTIFICATE-----\n" ); - - $options = array_merge( - [ - 'halt_on_error' => false, - 'verify' => $bad_cacert_path, - ], - $additional_options - ); - - if ( false !== $exception ) { - $this->expectException( $exception ); - $this->expectExceptionMessage( $exception_message ); - } - - $logger = new Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - Utils\http_request( 'GET', 'https://example.com', null, [], $options ); - - // Restore. - WP_CLI::set_logger( $prev_logger ); - - $this->assertTrue( empty( $logger->stdout ) ); - $this->assertNotFalse( strpos( $logger->stderr, $exception_message ) ); - } - - /** - * @dataProvider dataHttpRequestVerify - */ - #[DataProvider( 'dataHttpRequestVerify' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testHttpRequestVerify( $expected, $options ): void { - $transport_spy = new Mock_Requests_Transport(); - $options['transport'] = $transport_spy; - - Utils\http_request( 'GET', 'https://wordpress.org', null /*data*/, [] /*headers*/, $options ); - - $this->assertCount( 1, $transport_spy->requests ); - $this->assertEquals( $expected, $transport_spy->requests[0]['options']['verify'] ); - } - - public static function dataHttpRequestVerify(): array { - return [ - 'not passed' => [ - true, - [], - ], - 'true' => [ - true, - [ 'verify' => true ], - ], - 'false' => [ - false, - [ 'verify' => false ], - ], - 'custom cacert' => [ - __FILE__, - [ 'verify' => __FILE__ ], - ], - ]; - } - - public function testGetDefaultCaCert(): void { - $default_cert = Utils\get_default_cacert(); - $this->assertStringEndsWith( - '/rmccue/requests/certificates/cacert.pem', - $default_cert - ); - $this->assertFileExists( $default_cert ); - } - - /** - * @dataProvider dataPastTenseVerb - */ - #[DataProvider( 'dataPastTenseVerb' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testPastTenseVerb( $verb, $expected ): void { - $this->assertSame( $expected, Utils\past_tense_verb( $verb ) ); - } - - public static function dataPastTenseVerb(): array { - return [ - // Known to be used by commands. - [ 'activate', 'activated' ], - [ 'deactivate', 'deactivated' ], - [ 'delete', 'deleted' ], - [ 'import', 'imported' ], - [ 'install', 'installed' ], - [ 'network activate', 'network activated' ], - [ 'network deactivate', 'network deactivated' ], - [ 'regenerate', 'regenerated' ], - [ 'reset', 'reset' ], - [ 'spam', 'spammed' ], - [ 'toggle', 'toggled' ], - [ 'uninstall', 'uninstalled' ], - [ 'update', 'updated' ], - // Some others. - [ 'call', 'called' ], - [ 'check', 'checked' ], - [ 'crop', 'cropped' ], - [ 'fix', 'fixed' ], // One vowel + final "x" excluded. - [ 'ah', 'ahed' ], // One vowel + final "h" excluded. - [ 'show', 'showed' ], // One vowel + final "w" excluded. - [ 'ski', 'skied' ], - [ 'slay', 'slayed' ], // One vowel + final "y" excluded (nearly all irregular anyway). - [ 'submit', 'submited' ], // BUG: multi-voweled verbs that double not catered for - should be "submitted". - [ 'try', 'tried' ], - ]; - } - - /** - * @dataProvider dataExpandGlobs - */ - #[DataProvider( 'dataExpandGlobs' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testExpandGlobs( $path, $expected ): void { - $expand_globs_no_glob_brace = getenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ); - - $dir = __DIR__ . '/data/expand_globs/'; - $concat = function ( $v ) use ( $dir ) { - return $dir . $v; - }; - $expected = array_map( $concat, $expected ); - sort( $expected ); - - putenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=0' ); - $out = Utils\expand_globs( $dir . $path ); - sort( $out ); - $this->assertSame( $expected, $out ); - - putenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=1' ); - $out = Utils\expand_globs( $dir . $path ); - sort( $out ); - $this->assertSame( $expected, $out ); - - putenv( false === $expand_globs_no_glob_brace ? 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' : "WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=$expand_globs_no_glob_brace" ); - } - - public static function dataExpandGlobs(): array { - // Files in "data/expand_globs": foo.ab1, foo.ab2, foo.efg1, foo.efg2, bar.ab1, bar.ab2, baz.ab1, baz.ac1, baz.efg2. - return [ - [ 'foo.ab1', [ 'foo.ab1' ] ], - [ '{foo,bar}.ab1', [ 'foo.ab1', 'bar.ab1' ] ], - [ '{foo,baz}.a{b,c}1', [ 'foo.ab1', 'baz.ab1', 'baz.ac1' ] ], - [ '{foo,baz}.{ab,ac}1', [ 'foo.ab1', 'baz.ab1', 'baz.ac1' ] ], - [ '{foo,bar}.{ab1,efg1}', [ 'foo.ab1', 'foo.efg1', 'bar.ab1' ] ], - [ '{foo,bar,baz}.{ab,ac,efg}1', [ 'foo.ab1', 'foo.efg1', 'bar.ab1', 'baz.ab1', 'baz.ac1' ] ], - [ '{foo,ba{r,z}}.ab1', [ 'foo.ab1', 'bar.ab1', 'baz.ab1' ] ], - [ '{foo,ba{r,z}}.{ab1,efg1}', [ 'foo.ab1', 'foo.efg1', 'bar.ab1', 'baz.ab1' ] ], - [ '{foo,bar}.{ab{1,2},efg1}', [ 'foo.ab1', 'foo.ab2', 'foo.efg1', 'bar.ab1', 'bar.ab2' ] ], - [ '{foo,ba{r,z}}.{a{b,c}{1,2},efg{1,2}}', [ 'foo.ab1', 'foo.ab2', 'foo.efg1', 'foo.efg2', 'bar.ab1', 'bar.ab2', 'baz.ab1', 'baz.ac1', 'baz.efg2' ] ], - - [ 'no_such_file', [ 'no_such_file' ] ], // Documenting this behaviour here, which is odd (though advertized) - more natural to return an empty array. - ]; - } - - /** - * @dataProvider dataReportBatchOperationResults - */ - #[DataProvider( 'dataReportBatchOperationResults' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testReportBatchOperationResults( $stdout, $stderr, $noun, $verb, $total, $successes, $failures, $skips ): void { - // Save WP_CLI state. - $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $class_wp_cli_capture_exit->setAccessible( true ); - } - $prev_capture_exit = $class_wp_cli_capture_exit->getValue(); - - $prev_logger = WP_CLI::get_logger(); - - // Enable exit exception. - $class_wp_cli_capture_exit->setValue( null, true ); - - $logger = new Loggers\Execution(); - WP_CLI::set_logger( $logger ); - - $exception = null; - - try { - Utils\report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips ); - } catch ( ExitException $ex ) { - $exception = $ex; - } - $this->assertSame( $stdout, $logger->stdout ); - $this->assertSame( $stderr, $logger->stderr ); - - // Restore. - $class_wp_cli_capture_exit->setValue( null, $prev_capture_exit ); - WP_CLI::set_logger( $prev_logger ); - } - - public static function dataReportBatchOperationResults(): array { - return [ - [ "Success: Noun already verbed.\n", '', 'noun', 'verb', 1, 0, 0, null ], - [ "Success: Verbed 1 of 1 nouns.\n", '', 'noun', 'verb', 1, 1, 0, null ], - [ "Success: Verbed 1 of 2 nouns.\n", '', 'noun', 'verb', 2, 1, 0, null ], - [ "Success: Verbed 2 of 2 nouns.\n", '', 'noun', 'verb', 2, 2, 0, 0 ], - [ "Success: Verbed 1 of 2 nouns (1 skipped).\n", '', 'noun', 'verb', 2, 1, 0, 1 ], - [ "Success: Verbed 2 of 4 nouns (2 skipped).\n", '', 'noun', 'verb', 4, 2, 0, 2 ], - [ '', "Error: No nouns verbed.\n", 'noun', 'verb', 1, 0, 1, null ], - [ '', "Error: No nouns verbed.\n", 'noun', 'verb', 2, 0, 1, null ], - [ '', "Error: No nouns verbed (2 failed).\n", 'noun', 'verb', 3, 0, 2, 0 ], - [ '', "Error: No nouns verbed (2 failed, 1 skipped).\n", 'noun', 'verb', 3, 0, 2, 1 ], - [ '', "Error: Only verbed 1 of 2 nouns.\n", 'noun', 'verb', 2, 1, 1, null ], - [ '', "Error: Only verbed 1 of 3 nouns (2 failed).\n", 'noun', 'verb', 3, 1, 2, 0 ], - [ '', "Error: Only verbed 1 of 6 nouns (3 failed, 2 skipped).\n", 'noun', 'verb', 6, 1, 3, 2 ], - ]; - } - - public function testGetPHPBinary(): void { - $env_php_used = getenv( 'WP_CLI_PHP_USED' ); - $env_php = getenv( 'WP_CLI_PHP' ); - - putenv( 'WP_CLI_PHP_USED' ); - putenv( 'WP_CLI_PHP' ); - $get_php_binary = Utils\get_php_binary(); - $this->assertTrue( is_executable( $get_php_binary ) ); - - putenv( 'WP_CLI_PHP_USED=/my-php-5.3' ); - putenv( 'WP_CLI_PHP' ); - $get_php_binary = Utils\get_php_binary(); - $this->assertSame( $get_php_binary, '/my-php-5.3' ); - - putenv( 'WP_CLI_PHP=/my-php-7.3' ); - $get_php_binary = Utils\get_php_binary(); - $this->assertSame( $get_php_binary, '/my-php-5.3' ); // WP_CLI_PHP_USED wins. - - putenv( 'WP_CLI_PHP_USED' ); - $get_php_binary = Utils\get_php_binary(); - $this->assertSame( $get_php_binary, '/my-php-7.3' ); - - putenv( false === $env_php_used ? 'WP_CLI_PHP_USED' : "WP_CLI_PHP_USED=$env_php_used" ); - putenv( false === $env_php ? 'WP_CLI_PHP' : "WP_CLI_PHP=$env_php" ); - } - - /** - * @dataProvider dataProcOpenCompatWinEnv - */ - #[DataProvider( 'dataProcOpenCompatWinEnv' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testProcOpenCompatWinEnv( $cmd, $env, $expected_cmd, $expected_env ): void { - $env_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); - - putenv( 'WP_CLI_TEST_IS_WINDOWS=1' ); - - $cmd = Utils\_proc_open_compat_win_env( $cmd, $env ); - $this->assertSame( $expected_cmd, $cmd ); - $this->assertSame( $expected_env, $env ); - - putenv( false === $env_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$env_is_windows" ); - } - - public static function dataProcOpenCompatWinEnv(): array { - return [ - [ 'echo', [], 'echo', [] ], - [ 'ENV=blah echo', [], 'echo', [ 'ENV' => 'blah' ] ], - [ 'ENV="blah blah" echo', [], 'echo', [ 'ENV' => 'blah blah' ] ], - [ 'ENV_1="blah1 blah1" ENV_2="blah2" ENV_3=blah3 echo', [], 'echo', [ 'ENV_1' => 'blah1 blah1', 'ENV_2' => 'blah2', 'ENV_3' => 'blah3' ] ], - [ 'ENV= echo', [], 'echo', [ 'ENV' => '' ] ], - [ 'ENV=0 echo', [], 'echo', [ 'ENV' => '0' ] ], - - // With `$env` set. - [ 'echo', [ 'ENV' => 'in' ], 'echo', [ 'ENV' => 'in' ] ], - [ 'ENV=blah echo', [ 'ENV_1' => 'in1', 'ENV_2' => 'in2' ], 'echo', [ 'ENV_1' => 'in1', 'ENV_2' => 'in2', 'ENV' => 'blah' ] ], - [ 'ENV="blah blah" echo', [ 'ENV' => 'in' ], 'echo', [ 'ENV' => 'blah blah' ] ], - - // Special cases. - [ '1=1 echo', [], '1=1 echo', [] ], // Must begin with alphabetic or underscore. - [ '_eNv=1 echo', [], 'echo', [ '_eNv' => '1' ] ], // Mixed-case and beginning with underscore allowed. - [ 'ENV=\'blah blah\' echo', [], 'blah\' echo', [ 'ENV' => '\'blah' ] ], // Unix escaping not supported, ie treated literally. - ]; - } - - public static function dataEscLike(): array { - return [ - [ 'howdy%', 'howdy\\%' ], - [ 'howdy_', 'howdy\\_' ], - [ 'howdy\\', 'howdy\\\\' ], - [ 'howdy\\howdy%howdy_', 'howdy\\\\howdy\\%howdy\\_' ], - [ 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?', 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?' ], - ]; - } - - /** - * @dataProvider dataEscLike - */ - #[DataProvider( 'dataEscLike' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function test_esc_like( $input, $expected ): void { - $this->assertEquals( $expected, Utils\esc_like( $input ) ); - } - - /** - * @dataProvider dataEscLike - */ - #[DataProvider( 'dataEscLike' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function test_esc_like_with_wpdb( $input, $expected ): void { - global $wpdb; - - $wpdb = $this->createMock( WP_CLI_Mock_WPDB::class ); - $wpdb->method( 'esc_like' ) - ->willReturn( addcslashes( $input, '_%\\' ) ); - - $this->assertEquals( $expected, Utils\esc_like( $input ) ); - $this->assertEquals( $expected, Utils\esc_like( $input ) ); - } - - /** - * @dataProvider dataEscLike - */ - #[DataProvider( 'dataEscLike' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function test_esc_like_with_wpdb_being_null( $input, $expected ): void { - global $wpdb; - $wpdb = null; - $this->assertEquals( $expected, Utils\esc_like( $input ) ); - } - - /** - * @dataProvider dataIsJson - */ - #[DataProvider( 'dataIsJson' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testIsJson( $argument, $ignore_scalars, $expected ): void { - $this->assertEquals( $expected, Utils\is_json( $argument, $ignore_scalars ) ); - } - - public static function dataIsJson(): array { - return [ - [ '42', true, false ], - [ '42', false, true ], - [ '"test"', true, false ], - [ '"test"', false, true ], - [ '{"key1":"value1","key2":"value2"}', true, true ], - [ '{"key1":"value1","key2":"value2"}', false, true ], - [ '["value1","value2"]', true, true ], - [ '["value1","value2"]', false, true ], - [ '0', true, false ], - [ '0', false, true ], - [ '', true, false ], - [ '', false, false ], - ]; - } - - /** - * @dataProvider dataParseShellArray - */ - #[DataProvider( 'dataParseShellArray' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testParseShellArray( $assoc_args, $array_arguments, $expected ): void { - $this->assertEquals( $expected, Utils\parse_shell_arrays( $assoc_args, $array_arguments ) ); - } - - public static function dataParseShellArray(): array { - return [ - [ [ 'alpha' => '{"key":"value"}' ], [], [ 'alpha' => '{"key":"value"}' ] ], - [ [ 'alpha' => '{"key":"value"}' ], [ 'alpha' ], [ 'alpha' => [ 'key' => 'value' ] ] ], - [ [ 'alpha' => '{"key":"value"}' ], [ 'beta' ], [ 'alpha' => '{"key":"value"}' ] ], - ]; - } - - /** - * @dataProvider dataPluralize - */ - #[DataProvider( 'dataPluralize' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testPluralize( $singular, $count, $expected ): void { - $this->assertEquals( $expected, Utils\pluralize( $singular, $count ) ); - } - - public static function dataPluralize(): array { - return [ - [ 'string', 1, 'string' ], - [ 'string', 2, 'strings' ], - [ 'string', null, 'strings' ], - ]; - } - - /** - * @dataProvider dataPickFields - */ - #[DataProvider( 'dataPickFields' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testPickFields( $data, $fields, $expected ): void { - $this->assertEquals( $expected, Utils\pick_fields( $data, $fields ) ); - } - - public static function dataPickFields(): array { - return [ - [ [ 'keyA' => 'valA', 'keyB' => 'valB', 'keyC' => 'valC' ], [ 'keyB' ], [ 'keyB' => 'valB' ] ], - [ [ '1' => 'valA', '2' => 'valB', '3' => 'valC' ], [ '2' ], [ '2' => 'valB' ] ], - [ [ 1 => 'valA', 2 => 'valB', 3 => 'valC' ], [ 2 ], [ 2 => 'valB' ] ], - [ (object) [ 'keyA' => 'valA', 'keyB' => 'valB', 'keyC' => 'valC' ], [ 'keyB' ], [ 'keyB' => 'valB' ] ], - [ [], [ 'keyB' ], [ 'keyB' => null ] ], - [ [ 'keyA' => 'valA', 'keyB' => 'valB', 'keyC' => 'valC' ], [ 'keyD' ], [ 'keyD' => null ] ], - [ [ 'keyA' => 'valA', 'keyB' => 'valB', 'keyC' => 'valC' ], [ 'keyA', 'keyB', 'keyC', 'keyD' ], [ 'keyA' => 'valA', 'keyB' => 'valB', 'keyC' => 'valC', 'keyD' => null ] ], - ]; - } - - /** - * @dataProvider dataParseUrl - */ - #[DataProvider( 'dataParseUrl' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testParseUrl( $url, $component, $auto_add_scheme, $expected ): void { - $this->assertEquals( $expected, Utils\parse_url( $url, $component, $auto_add_scheme ) ); - } - - public static function dataParseUrl(): array { - return [ - [ 'http://user:pass@example.com:9090/path?arg=value#anchor', -1, true, [ 'scheme' => 'http', 'host' => 'example.com', 'port' => 9090, 'user' => 'user', 'pass' => 'pass', 'path' => '/path', 'query' => 'arg=value', 'fragment' => 'anchor' ] ], - [ 'example.com:9090/path?arg=value#anchor', -1, true, [ 'scheme' => 'http', 'host' => 'example.com', 'port' => 9090, 'path' => '/path', 'query' => 'arg=value', 'fragment' => 'anchor' ] ], - [ 'example.com:9090/path?arg=value#anchor', -1, false, [ 'host' => 'example.com', 'port' => 9090, 'path' => '/path', 'query' => 'arg=value', 'fragment' => 'anchor' ] ], - [ 'https://example.com', PHP_URL_HOST, true, 'example.com' ], - ]; - } - - /** - * @dataProvider dataEscapeCsvValue - */ - #[DataProvider( 'dataEscapeCsvValue' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testEscapeCsvValue( $input, $expected ): void { - $this->assertEquals( $expected, Utils\escape_csv_value( $input ) ); - } - - public static function dataEscapeCsvValue(): array { - return [ - // Values starting with special characters that should be escaped. - [ '=formula', "'=formula" ], - [ '+positive', "'+positive" ], - [ '-negative', "'-negative" ], - [ '@mention', "'@mention" ], - [ "\tindented", "'\tindented" ], - [ "\rcarriage", "'\rcarriage" ], - - // Values that should not be escaped. - [ 'normal text', 'normal text' ], - [ 'text with = in middle', 'text with = in middle' ], - [ '123', '123' ], - [ '', '' ], - [ ' leading space', ' leading space' ], - [ 'trailing space ', 'trailing space ' ], - [ '=x==y=', "'=x==y=" ], // Only escapes when the first character is special - ]; - } - - public function testWriteCsv(): void { - // Create a temporary file - $temp_file = tmpfile(); - - // Test data with various cases that need escaping - $headers = [ 'name', 'formula', 'quoted', 'comma', 'backslash' ]; - $rows = [ - [ - 'name' => 'John Doe', - 'formula' => '=SUM(A1:A2)', - 'quoted' => 'Contains "quotes"', - 'comma' => 'Item 1, Item 2', - 'backslash' => 'C:\\path\\to\\file', - ], - [ - 'name' => '@username', - 'formula' => '+1234', - 'quoted' => "'Single quotes'", - 'comma' => '-123,45', - 'backslash' => 'Escape \\this', - ], - ]; - - // Write to CSV - Utils\write_csv( $temp_file, $rows, $headers ); - - // Rewind file and read contents - rewind( $temp_file ); - $csv_content = stream_get_contents( $temp_file ); - - $this->assertNotFalse( $csv_content ); - - // Normalize line endings for cross-platform testing - $csv_content = str_replace( "\r\n", "\n", $csv_content ); - - // Check individual components instead of the exact string - $this->assertStringContainsString( 'name,formula,quoted,comma,backslash', $csv_content ); - $this->assertStringContainsString( '"John Doe"', $csv_content ); - $this->assertStringContainsString( '\'=SUM(A1:A2)', $csv_content ); - $this->assertStringContainsString( '"Contains ""quotes"""', $csv_content ); - $this->assertStringContainsString( '"Item 1, Item 2"', $csv_content ); - $this->assertStringContainsString( '\'@username', $csv_content ); - $this->assertStringContainsString( '\'Single quotes\'', $csv_content ); - $this->assertStringContainsString( '\'+1234', $csv_content ); - $this->assertStringContainsString( '\'-123,45', $csv_content ); - } - - public function testWriteCsvWithoutHeaders(): void { - // Create a temporary file - $temp_file = tmpfile(); - - // Test data without using headers - $rows = [ - [ 'John Doe', '=SUM(A1:A2)', 'Contains "quotes"' ], - [ '@username', '+1234', '-amount' ], - ]; - - // Write to CSV without headers - Utils\write_csv( $temp_file, $rows ); - - // Rewind file and read contents - rewind( $temp_file ); - $csv_content = stream_get_contents( $temp_file ); - - $this->assertNotFalse( $csv_content ); - - // Normalize line endings for cross-platform testing - $csv_content = str_replace( "\r\n", "\n", $csv_content ); - - // Check individual components instead of the exact string - $this->assertStringContainsString( '"John Doe"', $csv_content ); - $this->assertStringContainsString( '\'=SUM(A1:A2)', $csv_content ); - $this->assertStringContainsString( '"Contains ""quotes"""', $csv_content ); - $this->assertStringContainsString( '\'@username', $csv_content ); - $this->assertStringContainsString( '\'+1234', $csv_content ); - $this->assertStringContainsString( '\'-amount', $csv_content ); - } - - public function testWriteCsvWithFieldPicking(): void { - // Create a temporary file - $temp_file = tmpfile(); - - // Test data with additional fields that should be filtered out - $rows = [ - [ - 'id' => 1, - 'name' => 'John Doe', - 'email' => 'john@example.com', - 'formula' => '=HYPERLINK("http://malicious.com")', - 'extra' => 'Should not appear', - ], - [ - 'id' => 2, - 'name' => '@username', - 'email' => 'user@example.com', - 'formula' => '+1234', - 'extra' => 'Should not appear', - ], - ]; - - // Only include these headers (should filter the rows accordingly) - $headers = [ 'id', 'name', 'email', 'formula' ]; - - // Write to CSV, which should filter fields based on headers - Utils\write_csv( $temp_file, $rows, $headers ); - - // Rewind file and read contents - rewind( $temp_file ); - $csv_content = stream_get_contents( $temp_file ); - - $this->assertNotFalse( $csv_content ); - - // Normalize line endings for cross-platform testing - $csv_content = str_replace( "\r\n", "\n", $csv_content ); - - // Check individual components instead of the exact string - $this->assertStringContainsString( 'id,name,email,formula', $csv_content ); - $this->assertStringContainsString( '1,"John Doe",john@example.com', $csv_content ); - $this->assertStringContainsString( '\'=HYPERLINK', $csv_content ); - $this->assertStringContainsString( '2,\'@username,user@example.com', $csv_content ); - $this->assertStringContainsString( '\'+1234', $csv_content ); - - // Make sure 'extra' field is not in the output - $this->assertStringNotContainsString( 'extra', $csv_content ); - $this->assertStringNotContainsString( 'Should not appear', $csv_content ); - } - - public function testReplacePathConstsAddSlashes(): void { - $expected = "define( 'ABSPATH', dirname( 'C:\\\\Users\\\\test\'s\\\\site' ) . '/' );"; - $source = "define( 'ABSPATH', dirname( __FILE__ ) . '/' );"; - $actual = Utils\replace_path_consts( $source, "C:\Users\\test's\site" ); - $this->assertSame( $expected, $actual ); - } - - /** - * @dataProvider dataValidClassAndMethodPair - */ - #[DataProvider( 'dataValidClassAndMethodPair' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testValidClassAndMethodPair( $pair, $is_valid ): void { - $this->assertEquals( $is_valid, Utils\is_valid_class_and_method_pair( $pair ) ); - } - - public static function dataValidClassAndMethodPair(): array { - return [ - [ 'string', false ], - [ [], false ], - [ [ 'WP_CLI' ], false ], - [ [ true, false ], false ], - [ [ 'WP_CLI', 'invalid_method' ], false ], - [ [ 'Invalid_Class', 'invalid_method' ], false ], - [ [ 'WP_CLI', 'add_command' ], true ], - [ [ 'Exception', 'getMessage' ], true ], - ]; - } - - /** - * @dataProvider dataFormatBytesString - */ - #[DataProvider( 'dataFormatBytesString' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testFormatBytesString( $bytes, $decimals, $unit, $expected ) { - $actual = Utils\format_bytes_string( $bytes, $decimals, $unit ); - $this->assertSame( $expected, $actual, "Failed asserting that format_bytes_string($bytes, $decimals, '$unit') equals '$expected'." ); - } - - public static function dataFormatBytesString(): array { - return [ - [ 0, 2, '', '0 B' ], - [ '0', 2, '', '0 B' ], - [ -0, 2, '', '0 B' ], - [ 500, 2, '', '500 B' ], - [ 1000, 2, '', '1 KB' ], - [ 1500, 2, '', '1.5 KB' ], - [ 1536, 2, '', '1.54 KB' ], - [ 1048576, 2, '', '1.05 MB' ], - [ 1073741824, 2, '', '1.07 GB' ], - [ 1099511627776, 2, '', '1.1 TB' ], - [ 1000, 0, 'KB', '1 KB' ], - [ 1536, 1, '', '1.5 KB' ], - [ 1048576, 3, '', '1.049 MB' ], - [ 1000000, 0, 'MB', '1 MB' ], - [ 1000000000, 0, 'GB', '1 GB' ], - [ 1000000000000, 0, 'TB', '1 TB' ], - [ -1000000, 0, 'MB', '-1 MB' ], - [ -1536, 1, '', '-1.5 KB' ], - [ 5000, 0, 'FOO', '5 KB' ], - [ 1.5e26, 0, '', '150 YB' ], - ]; - } - - public function testExpandTildePath(): void { - $home = Utils\get_home_dir(); - - // Test tilde expansion for home directory - $this->assertEquals( $home, Utils\expand_tilde_path( '~' ) ); - - // Test tilde expansion with subdirectory - $this->assertEquals( $home . '/sites/wordpress', Utils\expand_tilde_path( '~/sites/wordpress' ) ); - - // Test that paths without tilde are unchanged - $this->assertEquals( '/absolute/path', Utils\expand_tilde_path( '/absolute/path' ) ); - $this->assertEquals( 'relative/path', Utils\expand_tilde_path( 'relative/path' ) ); - - // Test that tilde in the middle is not expanded - $this->assertEquals( '/path/to/~something', Utils\expand_tilde_path( '/path/to/~something' ) ); - } - - public function testEscapeshellargPreserveTilde() { - // Test that ~/ prefix is preserved and remainder is escaped - $this->assertEquals( '~/' . escapeshellarg( 'sites/wordpress' ), Utils\escapeshellarg_preserve_tilde( '~/sites/wordpress' ) ); - $this->assertEquals( '~/' . escapeshellarg( 'my documents/site' ), Utils\escapeshellarg_preserve_tilde( '~/my documents/site' ) ); - $this->assertEquals( '~/' . escapeshellarg( 'path with spaces' ), Utils\escapeshellarg_preserve_tilde( '~/path with spaces' ) ); - - // Test edge case: exactly ~/ - $this->assertEquals( '~/' . escapeshellarg( '' ), Utils\escapeshellarg_preserve_tilde( '~/' ) ); - - // Test that paths without ~/ are fully escaped - $this->assertEquals( escapeshellarg( '/absolute/path' ), Utils\escapeshellarg_preserve_tilde( '/absolute/path' ) ); - $this->assertEquals( escapeshellarg( 'relative/path' ), Utils\escapeshellarg_preserve_tilde( 'relative/path' ) ); - $this->assertEquals( escapeshellarg( '/path with spaces' ), Utils\escapeshellarg_preserve_tilde( '/path with spaces' ) ); - - // Test that lone ~ or ~username patterns are fully escaped (only ~/ is expanded) - $this->assertEquals( escapeshellarg( '~' ), Utils\escapeshellarg_preserve_tilde( '~' ) ); - $this->assertEquals( escapeshellarg( '~user' ), Utils\escapeshellarg_preserve_tilde( '~user' ) ); - $this->assertEquals( escapeshellarg( '~user/path' ), Utils\escapeshellarg_preserve_tilde( '~user/path' ) ); - } - - public function testHasStdinReturnsFalseForDevNull(): void { - if ( Utils\is_windows() ) { - $this->markTestSkipped( 'Stdin redirection from /dev/null not supported on Windows.' ); - } - - // Simulate non-interactive environments (cron, atd, puppet exec) where - // STDIN is connected to /dev/null. has_stdin() must return false. - $process = \WP_CLI\Process::create( $this->buildHasStdinCommand() . ' < /dev/null' )->run(); - - $this->assertSame( 'false', $process->stdout ); - } - - public function testHasStdinReturnsTrueForPipedData(): void { - if ( Utils\is_windows() ) { - $this->markTestSkipped( 'Piped stdin not supported on Windows.' ); - } - - // Simulate a real pipe with data: has_stdin() must return true. - $process = \WP_CLI\Process::create( 'echo somedata | ' . $this->buildHasStdinCommand() )->run(); - - $this->assertSame( 'true', $process->stdout ); - } - - private function buildHasStdinCommand(): string { - $php = Utils\get_php_binary(); - $root = WP_CLI_ROOT; - $code = sprintf( - 'require %s; require %s; echo WP_CLI\Utils\has_stdin() ? "true" : "false";', - var_export( $root . '/vendor/autoload.php', true ), - var_export( $root . '/php/utils.php', true ) - ); - - return escapeshellarg( $php ) . ' -r ' . escapeshellarg( $code ); - } -} diff --git a/tests/WPCLITest.php b/tests/WPCLITest.php deleted file mode 100644 index 3a5856e83..000000000 --- a/tests/WPCLITest.php +++ /dev/null @@ -1,84 +0,0 @@ -assertSame( WP_CLI\Utils\get_php_binary(), WP_CLI::get_php_binary() ); - } - - public function testErrorToString(): void { - $this->expectException( 'InvalidArgumentException' ); - $this->expectExceptionMessage( "Unsupported argument type passed to WP_CLI::error_to_string(): 'boolean'" ); - // @phpstan-ignore argument.type - WP_CLI::error_to_string( true ); - } - - /** - * @dataProvider data_print_value - */ - #[DataProvider( 'data_print_value' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function test_print_value( $value, $assoc_args, $expected_contains ): void { - ob_start(); - WP_CLI::print_value( $value, $assoc_args ); - $out = (string) ob_get_clean(); - - $this->assertStringContainsString( $expected_contains, $out ); - } - - /** - * @return array, 2: string}> - */ - public static function data_print_value(): array { - return [ - 'json format scalar' => [ - 'hello', - [ 'format' => 'json' ], - '"hello"' . "\n", - ], - 'json format array' => [ - [ 'a' => 1 ], - [ 'format' => 'json' ], - '{"a":1}' . "\n", - ], - 'yaml format array' => [ - [ 'a' => 1 ], - [ 'format' => 'yaml' ], - "a: 1\n", - ], - 'var_export format array' => [ - [ 'a' => 1 ], - [ 'format' => 'var_export' ], - "array (\n 'a' => 1,\n)", - ], - 'plaintext format array' => [ - [ 'a' => 1 ], - [ 'format' => 'plaintext' ], - "array (\n 'a' => 1,\n)", - ], - 'plaintext format scalar' => [ - 'hello', - [ 'format' => 'plaintext' ], - "hello\n", - ], - 'default format scalar' => [ - 'hello', - [], - "hello\n", - ], - 'default format array' => [ - [ 'a' => 1 ], - [], - "array (\n 'a' => 1,\n)", - ], - ]; - } -} diff --git a/tests/WP_CLI/Iterators/CSVTest.php b/tests/WP_CLI/Iterators/CSVTest.php deleted file mode 100644 index ea661cdda..000000000 --- a/tests/WP_CLI/Iterators/CSVTest.php +++ /dev/null @@ -1,112 +0,0 @@ -create_csv_file( - array( - array( 'foo', 'bar' ), - array( 'baz', 'qux' ), - ) - ); - - $expected = array( - 0 => array( - 'foo' => 'baz', - 'bar' => 'qux', - ), - ); - - foreach ( new CSV( $filename ) as $index => $row ) { - $this->assertEquals( $expected[ $index ], $row ); - } - } - - public function test_it_can_iterate_over_a_csv_file_with_custom_delimiter(): void { - $filename = $this->create_csv_file( - array( - array( 'foo|bar' ), - array( 'baz|qux' ), - ), - '|' - ); - - $expected = array( - 0 => array( - 'foo|bar' => 'baz|qux', - ), - ); - - foreach ( new CSV( $filename, '|' ) as $index => $row ) { - $this->assertEquals( $expected[ $index ], $row ); - } - } - - public function test_it_can_iterate_over_a_csv_file_with_multiple_lines_in_a_value(): void { - $filename = $this->create_csv_file( - array( - array( 'foo', "bar\nbaz" ), - array( 'qux', "quux\nquuz" ), - ) - ); - - $expected = array( - 0 => array( - 'foo' => 'qux', - "bar\nbaz" => "quux\nquuz", - ), - ); - - foreach ( new CSV( $filename ) as $index => $row ) { - $this->assertEquals( $expected[ $index ], $row ); - } - } - - public function test_it_can_iterate_over_a_csv_file_with_multiple_lines_and_comma_in_a_value(): void { - $filename = $this->create_csv_file( - array( - array( 'foo', "bar\nbaz,qux" ), - array( 'quux', "quuz\ncorge,grault" ), - ) - ); - - $expected = array( - 0 => array( - 'foo' => 'quux', - "bar\nbaz,qux" => "quuz\ncorge,grault", - ), - ); - - foreach ( new CSV( $filename ) as $index => $row ) { - $this->assertEquals( $expected[ $index ], $row ); - } - } - - private function create_csv_file( $data, $delimiter = ',' ) { - $filename = tempnam( sys_get_temp_dir(), 'wp-cli-tests-' ); - - /** - * @var resource $fp - */ - $fp = fopen( $filename, 'wb' ); - - foreach ( $data as $row ) { - fputcsv( $fp, $row, $delimiter, '"', '\\' ); - } - - fclose( $fp ); - - register_shutdown_function( - function () use ( $filename ) { - unlink( $filename ); - } - ); - - return $filename; - } -} diff --git a/tests/WP_CLI/Traversers/RecursiveDataStructureTraverserTest.php b/tests/WP_CLI/Traversers/RecursiveDataStructureTraverserTest.php deleted file mode 100644 index 1c0d22668..000000000 --- a/tests/WP_CLI/Traversers/RecursiveDataStructureTraverserTest.php +++ /dev/null @@ -1,152 +0,0 @@ - 'bar', - ); - - $traverser = new RecursiveDataStructureTraverser( $array ); - - $this->assertEquals( 'bar', $traverser->get( 'foo' ) ); - } - - public function test_it_can_get_a_top_level_object_value(): void { - $object = (object) array( - 'foo' => 'bar', - ); - - $traverser = new RecursiveDataStructureTraverser( $object ); - - $this->assertEquals( 'bar', $traverser->get( 'foo' ) ); - } - - public function test_it_can_get_a_nested_array_value(): void { - $array = array( - 'foo' => array( - 'bar' => array( - 'baz' => 'value', - ), - ), - ); - - $traverser = new RecursiveDataStructureTraverser( $array ); - - $this->assertEquals( 'value', $traverser->get( array( 'foo', 'bar', 'baz' ) ) ); - } - - public function test_it_can_get_a_nested_object_value(): void { - $object = (object) array( - 'foo' => (object) array( - 'bar' => 'baz', - ), - ); - - $traverser = new RecursiveDataStructureTraverser( $object ); - - $this->assertEquals( 'baz', $traverser->get( array( 'foo', 'bar' ) ) ); - } - - public function test_it_can_set_a_nested_array_value(): void { - $array = array( - 'foo' => array( - 'bar' => 'baz', - ), - ); - $this->assertEquals( 'baz', $array['foo']['bar'] ); - - $traverser = new RecursiveDataStructureTraverser( $array ); - $traverser->update( array( 'foo', 'bar' ), 'new' ); - - $this->assertEquals( 'new', $array['foo']['bar'] ); - } - - public function test_it_can_set_a_nested_object_value(): void { - $object = (object) array( - 'foo' => (object) array( - 'bar' => 'baz', - ), - ); - $this->assertEquals( 'baz', $object->foo->bar ); - - $traverser = new RecursiveDataStructureTraverser( $object ); - $traverser->update( array( 'foo', 'bar' ), 'new' ); - - $this->assertEquals( 'new', $object->foo->bar ); - } - - public function test_it_can_update_an_integer_object_value(): void { - $object = (object) array( - 'test_mode' => 0, - ); - $this->assertEquals( 0, $object->test_mode ); - - $traverser = new RecursiveDataStructureTraverser( $object ); - $traverser->update( array( 'test_mode' ), 1 ); - - $this->assertEquals( 1, $object->test_mode ); - } - - public function test_it_can_delete_a_nested_array_value(): void { - $array = array( - 'foo' => array( - 'bar' => 'baz', - ), - ); - $this->assertArrayHasKey( 'bar', $array['foo'] ); - - $traverser = new RecursiveDataStructureTraverser( $array ); - $traverser->delete( array( 'foo', 'bar' ) ); - - $this->assertArrayNotHasKey( 'bar', $array['foo'] ); - } - - public function test_it_can_delete_a_nested_object_value(): void { - $object = (object) array( - 'foo' => (object) array( - 'bar' => 'baz', - ), - ); - $this->assertObjectHasProperty( 'bar', $object->foo ); - - $traverser = new RecursiveDataStructureTraverser( $object ); - $traverser->delete( array( 'foo', 'bar' ) ); - - $this->assertObjectNotHasProperty( 'bar', $object->foo ); - } - - public function test_it_can_insert_a_key_into_a_nested_array(): void { - $array = array( - 'foo' => array( - 'bar' => 'baz', - ), - ); - - $traverser = new RecursiveDataStructureTraverser( $array ); - $traverser->insert( array( 'foo', 'new' ), 'new value' ); - - $this->assertArrayHasKey( 'new', $array['foo'] ); - $this->assertEquals( 'new value', $array['foo']['new'] ); - } - - public function test_it_throws_an_exception_when_attempting_to_create_a_key_on_an_invalid_type(): void { - $data = 'a string'; - $traverser = new RecursiveDataStructureTraverser( $data ); - - try { - $traverser->insert( array( 'key' ), 'value' ); - } catch ( \Exception $e ) { - // @phpstan-ignore method.alreadyNarrowedType - $this->assertSame( 'a string', $data ); - return; - } - - $this->fail( 'Failed to assert that an exception was thrown when inserting a key into a string.' ); - } -} diff --git a/tests/WP_CLI/WpOrgApiTest.php b/tests/WP_CLI/WpOrgApiTest.php deleted file mode 100644 index e63373c4a..000000000 --- a/tests/WP_CLI/WpOrgApiTest.php +++ /dev/null @@ -1,168 +0,0 @@ - [ - 'get_core_checksums', - [ 'version' => 'trunk' ], - [], - 'https://api.wordpress.org/core/checksums/1.0/?version=trunk&locale=en_US', - [], - ], - 'can retrieve core checksums for a specific locale' => [ - 'get_core_checksums', - [ - 'version' => '4.5', - 'locale' => 'de_DE', - ], - [], - 'https://api.wordpress.org/core/checksums/1.0/?version=4.5&locale=de_DE', - [], - ], - 'can retrieve plugin checksums' => [ - 'get_plugin_checksums', - [ - 'plugin' => 'hello-dolly', - 'version' => '1.0', - ], - [], - 'https://downloads.wordpress.org/plugin-checksums/hello-dolly/1.0.json', - [], - ], - 'can retrieve a core version check' => [ - 'get_core_version_check', - [], - [], - 'https://api.wordpress.org/core/version-check/1.7/?locale=en_US', - [], - ], - 'can retrieve a core version check for a specific locale' => [ - 'get_core_version_check', - [ 'locale' => 'de_DE' ], - [], - 'https://api.wordpress.org/core/version-check/1.7/?locale=de_DE', - [], - ], - 'can retrieve a download offer for core' => [ - 'get_core_download_offer', - [], - [], - 'https://api.wordpress.org/core/version-check/1.7/?locale=en_US', - [], - ], - 'can retrieve a download offer for core for a specific locale' => [ - 'get_core_download_offer', - [ 'locale' => 'de_DE' ], - [], - 'https://api.wordpress.org/core/version-check/1.7/?locale=de_DE', - [], - ], - 'can retrieve info for a plugin' => [ - 'get_plugin_info', - [ 'plugin' => 'hello-dolly' ], - [], - 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request%5Blocale%5D=en_US&request%5Bslug%5D=hello-dolly', - [], - ], - 'can retrieve info for a plugin for a specific locale' => [ - 'get_plugin_info', - [ - 'plugin' => 'hello-dolly', - 'locale' => 'de_DE', - ], - [], - 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request%5Blocale%5D=de_DE&request%5Bslug%5D=hello-dolly', - [], - ], - 'can retrieve info for a theme' => [ - 'get_theme_info', - [ 'theme' => 'twentytwenty' ], - [], - 'https://api.wordpress.org/themes/info/1.2/?action=theme_information&request%5Blocale%5D=en_US&request%5Bslug%5D=twentytwenty', - [], - ], - 'can retrieve salts' => [ - 'get_salts', - [], - [], - 'https://api.wordpress.org/secret-key/1.1/salt/', - [], - ], - 'defaults to secure requests' => [ - 'get_salts', - [], - [], - 'https://api.wordpress.org/secret-key/1.1/salt/', - [ 'verify' => true ], - ], - 'can explicitly request secure requests' => [ - 'get_salts', - [], - [ 'insecure' => false ], - 'https://api.wordpress.org/secret-key/1.1/salt/', - [ - 'insecure' => false, - 'verify' => true, - ], - ], - 'can explicitly request insecure requests' => [ - 'get_salts', - [], - [ 'insecure' => true ], - 'https://api.wordpress.org/secret-key/1.1/salt/', - [ - 'insecure' => true, - 'verify' => false, - ], - ], - ]; - } - - /** - * @dataProvider data_http_request_verify - */ - #[DataProvider( 'data_http_request_verify' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function test_http_request_verify( $method, $arguments, $options, $expected_url, $expected_options ): void { - if ( isset( $options['insecure'] ) && true === $options['insecure'] ) { - // Create temporary file to use as a bad certificate file. - $bad_cacert_path = tempnam( sys_get_temp_dir(), 'wp-cli-badcacert-pem-' ); - file_put_contents( - $bad_cacert_path, - "-----BEGIN CERTIFICATE-----\nasdfasdf\n-----END CERTIFICATE-----\n" - ); - - $options = array_merge( [ 'verify' => $bad_cacert_path ], $options ); - } - - $transport_spy = new Mock_Requests_Transport(); - $options['transport'] = $transport_spy; - $expected_options['transport'] = $transport_spy; - - $wp_org_api = new WpOrgApi( $options ); - try { - $wp_org_api->$method( ...array_values( $arguments ) ); - } catch ( RuntimeException $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch - } - - // Undo bad CAcert hack before asserting. - if ( isset( $bad_cacert_path ) ) { - unlink( $bad_cacert_path ); - } - - $this->assertCount( 1, $transport_spy->requests ); - $this->assertEquals( $expected_url, $transport_spy->requests[0]['url'] ); - foreach ( $expected_options as $key => $value ) { - $this->assertEquals( $value, $transport_spy->requests[0]['options'][ $key ] ); - } - } -} diff --git a/tests/WindowsArgsTest.php b/tests/WindowsArgsTest.php deleted file mode 100644 index 31b7a1d37..000000000 --- a/tests/WindowsArgsTest.php +++ /dev/null @@ -1,103 +0,0 @@ -getMethod( 'back_compat_conversions' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $method->setAccessible( true ); - } - - /** - * @var array{0: array, 1: array} $result - */ - $result = $method->invoke( null, $input_args, [] ); - [ $result_args, $_ ] = $result; - - // Verify the results - $this->assertCount( $expected_count, $result_args, 'Unexpected number of arguments' ); - - foreach ( $expected_values as $index => $expected_value ) { - $this->assertEquals( $expected_value, $result_args[ $index ], "Argument at index $index doesn't match" ); - } - } - - public static function provideWindowsArguments() { - return [ - // is_windows, input_args, expected_count, expected_values - 'Windows: space-separated IDs should be split' => [ - true, - [ 'post', 'delete', '123 456 789' ], - 5, - [ 'post', 'delete', '123', '456', '789' ], - ], - 'Windows: single ID should not be split' => [ - true, - [ 'post', 'delete', '123' ], - 3, - [ 'post', 'delete', '123' ], - ], - 'Windows: non-numeric strings should not be split' => [ - true, - [ 'post', 'delete', 'hello world' ], - 3, - [ 'post', 'delete', 'hello world' ], - ], - 'Windows: mixed args (numeric at start)' => [ - true, - [ 'post', 'delete', '123 456', 'some-slug' ], - 5, - [ 'post', 'delete', '123', '456', 'some-slug' ], - ], - 'Non-Windows: space-separated IDs should not split' => [ - false, - [ 'post', 'delete', '123 456' ], - 3, - [ 'post', 'delete', '123 456' ], - ], - 'Windows: IDs with tabs and spaces' => [ - true, - [ 'post', 'delete', "123\t456 789" ], - 5, - [ 'post', 'delete', '123', '456', '789' ], - ], - 'Windows: normal case without leading/trailing spaces' => [ - true, - [ 'post', 'delete', '123 456' ], - 4, - [ 'post', 'delete', '123', '456' ], - ], - 'Windows: leading/trailing spaces prevent splitting' => [ - true, - [ 'post', 'delete', ' 123 456 ' ], - 3, - [ 'post', 'delete', ' 123 456 ' ], - ], - ]; - } - - /** - * Cleanup after each test - */ - public function tearDown(): void { - putenv( 'WP_CLI_TEST_IS_WINDOWS' ); - parent::tearDown(); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 58bb79043..d0a2f3b59 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,20 +1,20 @@ requests[] = compact( 'url', 'headers', 'data', 'options' ); - - return 'HTTP/1.1 418' . "\r\n" - . 'Content-Type: water/leaf-infused' . "\r\n" - . "\r\n\r\n"; // This last line is actually important or the request will error. - } - - public function request_multiple( $requests, $options ) { - throw new Exception( 'Method not implemented: ' . __METHOD__ ); - } - - public static function test( $capabilities = [] ) { - return true; - } -} diff --git a/tests/phpunit6-compat.php b/tests/phpunit6-compat.php new file mode 100644 index 000000000..73d41cfed --- /dev/null +++ b/tests/phpunit6-compat.php @@ -0,0 +1,19 @@ +=' ) ) { + + class_alias( 'PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase' ); + class_alias( 'PHPUnit\Framework\Exception', 'PHPUnit_Framework_Exception' ); + class_alias( 'PHPUnit\Framework\ExpectationFailedException', 'PHPUnit_Framework_ExpectationFailedException' ); + class_alias( 'PHPUnit\Framework\Error\Notice', 'PHPUnit_Framework_Error_Notice' ); + class_alias( 'PHPUnit\Framework\Error\Warning', 'PHPUnit_Framework_Error_Warning' ); + class_alias( 'PHPUnit\Framework\Test', 'PHPUnit_Framework_Test' ); + class_alias( 'PHPUnit\Framework\Warning', 'PHPUnit_Framework_Warning' ); + class_alias( 'PHPUnit\Framework\AssertionFailedError', 'PHPUnit_Framework_AssertionFailedError' ); + class_alias( 'PHPUnit\Framework\TestSuite', 'PHPUnit_Framework_TestSuite' ); + class_alias( 'PHPUnit\Framework\TestListener', 'PHPUnit_Framework_TestListener' ); + class_alias( 'PHPUnit\Util\GlobalState', 'PHPUnit_Util_GlobalState' ); + class_alias( 'PHPUnit\Util\Getopt', 'PHPUnit_Util_Getopt' ); + +} diff --git a/tests/test-arg-validation.php b/tests/test-arg-validation.php new file mode 100644 index 000000000..4a3b3680c --- /dev/null +++ b/tests/test-arg-validation.php @@ -0,0 +1,64 @@ + []' ); + + $this->assertFalse( $validator->enough_positionals( array() ) ); + $this->assertTrue( $validator->enough_positionals( array( 1, 2 ) ) ); + $this->assertTrue( $validator->enough_positionals( array( 1, 2, 3, 4 ) ) ); + + $this->assertEquals( array( 4 ), $validator->unknown_positionals( array( 1, 2, 3, 4 ) ) ); + } + + function testRepeatingPositional() { + $validator = new SynopsisValidator( ' [...]' ); + + $this->assertFalse( $validator->enough_positionals( array() ) ); + $this->assertTrue( $validator->enough_positionals( array( 1 ) ) ); + $this->assertTrue( $validator->enough_positionals( array( 1, 2, 3 ) ) ); + + $this->assertEmpty( $validator->unknown_positionals( array( 1, 2, 3 ) ) ); + } + + function testUnknownAssocEmpty() { + $validator = new SynopsisValidator( '' ); + + $assoc_args = array( 'foo' => true, 'bar' => false ); + $this->assertEquals( array_keys( $assoc_args ), $validator->unknown_assoc( $assoc_args ) ); + } + + function testUnknownAssoc() { + $validator = new SynopsisValidator( '--type= [--brand=] [--flag]' ); + + $assoc_args = array( 'type' => 'analog', 'brand' => true, 'flag' => true ); + $this->assertEmpty( $validator->unknown_assoc( $assoc_args ) ); + + $assoc_args['another'] = true; + $this->assertContains( 'another', $validator->unknown_assoc( $assoc_args ) ); + } + + function testMissingAssoc() { + $validator = new SynopsisValidator( '--type= [--brand=] [--flag]' ); + + $assoc_args = array( 'brand' => true, 'flag' => true ); + list( $errors, $to_unset ) = $validator->validate_assoc( $assoc_args ); + + $this->assertCount( 1, $errors['fatal'] ); + $this->assertCount( 1, $errors['warning'] ); + } + + function testAssocWithOptionalValue() { + $validator = new SynopsisValidator( '[--network[=]]' ); + + $assoc_args = array( 'network' => true ); + list( $errors, $to_unset ) = $validator->validate_assoc( $assoc_args ); + + $this->assertCount( 0, $errors['fatal'] ); + $this->assertCount( 0, $errors['warning'] ); + } +} + diff --git a/tests/test-autoload-splitter.php b/tests/test-autoload-splitter.php new file mode 100644 index 000000000..88515d822 --- /dev/null +++ b/tests/test-autoload-splitter.php @@ -0,0 +1,65 @@ +assertSame( $expected, $autoload_splitter('foo', $code) ); + } + + /** + * Data provider of code paths. + * + * @return array + */ + public function dataCodePaths() { + return array( + array( '/wp-cli/a-command/', true ), + array( '/wp-cli/abcd-command/', true ), + array( '/wp-cli/a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-command/', true ), + array( 'xyz/wp-cli/abcd-command/zxy', true ), + + array( '/php/commands/src/', true ), + array( 'xyz/php/commands/src/zyx', true ), + + array( '/wp-cli/-command/', false ), // No command name. + array( '/wp-cli/--command/', false ), // No command name. + array( '/wp-cli/abcd-command-/', false ), // End is not '-command/` + array( '/wp-cli/abcd-/', false ), // End is not '-command/'. + array( '/wp-cli/abcd-command', false ), // End is not '-command/'. + array( 'wp-cli/abcd-command/', false ), // Start is not '/wp-cli/'. + array( '/wp--cli/abcd-command/', false ), // Start is not '/wp-cli/'. + array( '/wp-cliabcd-command/', false ), // Start is not '/wp-cli/'. + array( '/wp-cli//abcd-command/', false ), // Middle contains two '/'. + + array( '/php-/commands/src/', false ), // Start is not '/php/'. + array( 'php/commands/src/', false ), // Start is not '/php/'. + array( '/php/commands/src', false ), // End is not '/src/'. + array( '/php/commands/srcs/', false ), // End is not '/src/'. + array( '/php/commandssrc/', false ), // End is not '/src/'. + ); + } +} diff --git a/tests/test-behat-tags.php b/tests/test-behat-tags.php new file mode 100644 index 000000000..11ea159de --- /dev/null +++ b/tests/test-behat-tags.php @@ -0,0 +1,133 @@ +temp_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-behat-tags-', true ); + mkdir( $this->temp_dir ); + mkdir( $this->temp_dir . '/features' ); + } + + function tearDown() { + + if ( $this->temp_dir && file_exists( $this->temp_dir ) ) { + foreach ( glob( $this->temp_dir . '/features/*' ) as $feature_file ) { + unlink( $feature_file ); + } + rmdir( $this->temp_dir . '/features' ); + rmdir( $this->temp_dir ); + } + + parent::tearDown(); + } + + /** + * @dataProvider data_behat_tags_wp_version_github_token + */ + function test_behat_tags_wp_version_github_token( $env, $expected ) { + $env_wp_version = getenv( 'WP_VERSION' ); + $env_github_token = getenv( 'GITHUB_TOKEN' ); + + putenv( 'WP_VERSION' ); + putenv( 'GITHUB_TOKEN' ); + + $behat_tags = dirname( __DIR__ ) . '/ci/behat-tags.php'; + + $contents = '@require-wp-4.6 @require-wp-4.8 @require-wp-4.9 @less-than-wp-4.6 @less-than-wp-4.8 @less-than-wp-4.9'; + file_put_contents( $this->temp_dir . '/features/wp_version.feature', $contents ); + + $output = exec( "cd {$this->temp_dir}; $env php $behat_tags" ); + $this->assertSame( '--tags=' . $expected . '&&~@broken', $output ); + + putenv( false === $env_wp_version ? 'WP_VERSION' : "WP_VERSION=$env_wp_version" ); + putenv( false === $env_github_token ? 'GITHUB_TOKEN' : "GITHUB_TOKEN=$env_github_token" ); + } + + function data_behat_tags_wp_version_github_token() { + return array( + array( 'WP_VERSION=4.5', '~@require-wp-4.6&&~@require-wp-4.8&&~@require-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=4.6', '~@require-wp-4.8&&~@require-wp-4.9&&~@less-than-wp-4.6&&~@github-api' ), + array( 'WP_VERSION=4.7', '~@require-wp-4.8&&~@require-wp-4.9&&~@less-than-wp-4.6&&~@github-api' ), + array( 'WP_VERSION=4.8', '~@require-wp-4.9&&~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@github-api' ), + array( 'WP_VERSION=4.9', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=5.0', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=latest', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=trunk', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'WP_VERSION=nightly', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( '', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9&&~@github-api' ), + array( 'GITHUB_TOKEN=blah', '~@less-than-wp-4.6&&~@less-than-wp-4.8&&~@less-than-wp-4.9' ), + ); + } + + function test_behat_tags_php_version() { + $env_github_token = getenv( 'GITHUB_TOKEN' ); + + putenv( 'GITHUB_TOKEN' ); + + $behat_tags = dirname( __DIR__ ) . '/ci/behat-tags.php'; + + $php_version = substr( PHP_VERSION, 0, 3 ); + $contents = $expected = ''; + + if ( '5.3' === $php_version ) { + $contents = '@require-php-5.2 @require-php-5.3 @require-php-5.4 @less-than-php-5.2 @less-than-php-5.3 @less-than-php-5.4'; + $expected = '~@require-php-5.4&&~@less-than-php-5.2&&~@less-than-php-5.3'; + } elseif ( '5.4' === $php_version ) { + $contents = '@require-php-5.3 @require-php-5.4 @require-php-5.5 @less-than-php-5.3 @less-than-php-5.4 @less-than-php-5.5'; + $expected = '~@require-php-5.5&&~@less-than-php-5.3&&~@less-than-php-5.4'; + } elseif ( '5.5' === $php_version ) { + $contents = '@require-php-5.4 @require-php-5.5 @require-php-5.6 @less-than-php-5.4 @less-than-php-5.5 @less-than-php-5.6'; + $expected = '~@require-php-5.6&&~@less-than-php-5.4&&~@less-than-php-5.5'; + } elseif ( '5.6' === $php_version ) { + $contents = '@require-php-5.5 @require-php-5.6 @require-php-7.0 @less-than-php-5.5 @less-than-php-5.6 @less-than-php-7.0'; + $expected = '~@require-php-7.0&&~@less-than-php-5.5&&~@less-than-php-5.6'; + } elseif ( '7.0' === $php_version ) { + $contents = '@require-php-5.6 @require-php-7.0 @require-php-7.1 @less-than-php-5.6 @less-than-php-7.0 @less-than-php-7.1'; + $expected = '~@require-php-7.1&&~@less-than-php-5.6&&~@less-than-php-7.0'; + } elseif ( '7.1' === $php_version ) { + $contents = '@require-php-7.0 @require-php-7.1 @require-php-7.2 @less-than-php-7.0 @less-than-php-7.1 @less-than-php-7.2'; + $expected = '~@require-php-7.2&&~@less-than-php-7.0&&~@less-than-php-7.1'; + } elseif ( '7.2' === $php_version ) { + $contents = '@require-php-7.1 @require-php-7.2 @require-php-7.3 @less-than-php-7.1 @less-than-php-7.2 @less-than-php-7.3'; + $expected = '~@require-php-7.3&&~@less-than-php-7.1&&~@less-than-php-7.2'; + } else { + $this->markTestSkipped( "No test for PHP_VERSION $php_version." ); + } + + file_put_contents( $this->temp_dir . '/features/php_version.feature', $contents ); + + $output = exec( "cd {$this->temp_dir}; php $behat_tags" ); + $this->assertSame( '--tags=' . $expected . '&&~@github-api&&~@broken', $output ); + + putenv( false === $env_github_token ? 'GITHUB_TOKEN' : "GITHUB_TOKEN=$env_github_token" ); + } + + function test_behat_tags_extension() { + $env_github_token = getenv( 'GITHUB_TOKEN' ); + + putenv( 'GITHUB_TOKEN' ); + + $behat_tags = dirname( __DIR__ ) . '/ci/behat-tags.php'; + + file_put_contents( $this->temp_dir . '/features/extension.feature', '@require-extension-imagick @require-extension-curl' ); + + $expecteds = array(); + if ( ! extension_loaded( 'imagick' ) ) { + $expecteds[] = '~@require-extension-imagick'; + } + if ( ! extension_loaded( 'intl' ) ) { + $expecteds[] = '~@require-extension-intl'; + } + $expected = '--tags=' . implode( '&&', array_merge( array( '~@github-api', '~@broken' ), $expecteds ) ); + $output = exec( "cd {$this->temp_dir}; php $behat_tags" ); + $this->assertSame( $expected, $output ); + + putenv( false === $env_github_token ? 'GITHUB_TOKEN' : "GITHUB_TOKEN=$env_github_token" ); + } +} diff --git a/tests/test-bundled-commands.php b/tests/test-bundled-commands.php new file mode 100644 index 000000000..912f1620e --- /dev/null +++ b/tests/test-bundled-commands.php @@ -0,0 +1,43 @@ +assertEquals( $expected_result, $result ); + } + + public function dataProviderIsBundledCommands() { + return array( + // Bundled commands. + array( 'CLI_Command', true ), + array( new CLI_Command(), true ), + + // Commands not bundled. + array( 'Random_Unknown_Command', false ), + array( new Random_Unknown_Command(), false ), + + // Wrong data types. + array( array( 'CLI_Command' ), false ), + array( new stdClass(), false ), + array( 42, false ), + array( null, false ), + ); + } +} diff --git a/tests/CommandFactoryTest.php b/tests/test-commandfactory.php similarity index 62% rename from tests/CommandFactoryTest.php rename to tests/test-commandfactory.php index 6ead60c05..bf2ab11a5 100644 --- a/tests/CommandFactoryTest.php +++ b/tests/test-commandfactory.php @@ -1,19 +1,15 @@ setAccessible( true ); - } + $extract_last_doc_comment->setAccessible( true ); } $actual = $extract_last_doc_comment->invoke( null, $content ); @@ -37,8 +30,7 @@ public function testExtractLastDocComment( $content, $expected ): void { /** * @dataProvider dataProviderExtractLastDocComment */ - #[DataProvider( 'dataProviderExtractLastDocComment' )] // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPUnitAttributeFound - public function testExtractLastDocCommentWin( $content, $expected ): void { + function testExtractLastDocCommentWin( $content, $expected ) { // Save and set test env var. $is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); putenv( 'WP_CLI_TEST_IS_WINDOWS=1' ); @@ -46,10 +38,7 @@ public function testExtractLastDocCommentWin( $content, $expected ): void { static $extract_last_doc_comment = null; if ( null === $extract_last_doc_comment ) { $extract_last_doc_comment = new \ReflectionMethod( 'WP_CLI\Dispatcher\CommandFactory', 'extract_last_doc_comment' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $extract_last_doc_comment->setAccessible( true ); - } + $extract_last_doc_comment->setAccessible( true ); } $actual = $extract_last_doc_comment->invoke( null, $content ); @@ -59,52 +48,49 @@ public function testExtractLastDocCommentWin( $content, $expected ): void { putenv( false === $is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$is_windows" ); } - public static function dataProviderExtractLastDocComment(): array { - return [ - [ '', false ], - [ '*/', false ], - [ '/*/ ', false ], - [ '/**/', false ], - [ '/***/ */', false ], - [ '/***/', '/***/' ], - [ "\n /**\n \n \t\n */ \t\n \n ", "/**\n \n \t\n */" ], - [ "\r\n /**\r\n \r\n \t\r\n */ \t\r\n \r\n ", "/**\r\n \r\n \t\r\n */" ], - [ '/**/ /***/ /***/', '/***/' ], - [ 'asdfasdf/** /** */', '/** /** */' ], - [ '*//** /** */', '/** /** */' ], - [ '/** *//** /** */', '/** /** */' ], - [ '*//** */ /** /** */', '/** /** */' ], - [ '*//** *//** /** /** */', '/** /** /** */' ], - - [ '/** */class qwer', '/** */' ], - [ '/**1*/class qwer{}/**2*/class asdf', '/**2*/' ], - [ "/** */class qwer {}\nclass asdf", false ], - [ "/** */class qwer {}\r\nclass asdf", false ], - - [ '/** */function qwer', '/** */' ], - [ '/** */function qwer( $function ) {}', '/** */' ], - [ '/**1*/function qwer() {}/**2*/function asdf()', '/**2*/' ], - [ "/** */function qwer() {}\nfunction asdf()", false ], - [ "/** */function qwer() {}\r\nfunction asdf()", false ], - [ '/** */function qwer() {}function asdf()', false ], - [ '/** */function qwer() {};function asdf( $function )', false ], - ]; + function dataProviderExtractLastDocComment() { + return array( + array( "", false ), + array( "*/", false ), + array( "/*/ ", false ), + array( "/**/", false ), + array( "/***/ */", false ), + array( "/***/", "/***/" ), + array( "\n /**\n \n \t\n */ \t\n \n ", "/**\n \n \t\n */" ), + array( "\r\n /**\r\n \r\n \t\r\n */ \t\r\n \r\n ", "/**\r\n \r\n \t\r\n */" ), + array( "/**/ /***/ /***/", "/***/" ), + array( "asdfasdf/** /** */", "/** /** */" ), + array( "*//** /** */", "/** /** */" ), + array( "/** *//** /** */", "/** /** */" ), + array( "*//** */ /** /** */", "/** /** */" ), + array( "*//** *//** /** /** */", "/** /** /** */" ), + + array( "/** */class qwer", "/** */" ), + array( "/**1*/class qwer{}/**2*/class asdf", "/**2*/" ), + array( "/** */class qwer {}\nclass asdf", false ), + array( "/** */class qwer {}\r\nclass asdf", false ), + + array( "/** */function qwer", "/** */" ), + array( "/** */function qwer( \$function ) {}", "/** */" ), + array( "/**1*/function qwer() {}/**2*/function asdf()", "/**2*/" ), + array( "/** */function qwer() {}\nfunction asdf()", false ), + array( "/** */function qwer() {}\r\nfunction asdf()", false ), + array( "/** */function qwer() {}function asdf()", false ), + array( "/** */function qwer() {};function asdf( \$function )", false ), + ); } - public function testGetDocComment(): void { + function testGetDocComment() { // Save and set test env var. - $_get_doc_comment = getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ); - $_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); + $get_doc_comment = getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ); + $is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); putenv( 'WP_CLI_TEST_GET_DOC_COMMENT=1' ); putenv( 'WP_CLI_TEST_IS_WINDOWS=0' ); // Make private function accessible. $get_doc_comment = new \ReflectionMethod( 'WP_CLI\Dispatcher\CommandFactory', 'get_doc_comment' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $get_doc_comment->setAccessible( true ); - } + $get_doc_comment->setAccessible( true ); if ( ! class_exists( 'CommandFactoryTests_Get_Doc_Comment_1_Command', false ) ) { require __DIR__ . '/data/commandfactory-doc_comment-class.php'; @@ -113,174 +99,166 @@ public function testGetDocComment(): void { require __DIR__ . '/data/commandfactory-doc_comment-class-win.php'; } - // Class 1. + // Class 1 - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 2. + // Class method 2 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command2' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 3. + // Class method 3 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command3' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 4. + // Class method 4 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command4' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Class 1 Windows. + // Class 1 Windows - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 2. + // Class method 2 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command2' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 3. + // Class method 3 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command3' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 4. + // Class method 4 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command4' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Class 2. + // Class 2 - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Class 2 Windows. + // Class 2 Windows - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Functions. + // Functions require __DIR__ . '/data/commandfactory-doc_comment-function.php'; - // Function 1. + // Function 1 $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Function 2. + // Function 2 $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_2' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Function 3. + // Function 3 - // @phpstan-ignore variable.undefined $reflection = new \ReflectionFunction( $commandfactorytests_get_doc_comment_func_3 ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); // Restore. - putenv( false === $_get_doc_comment ? 'WP_CLI_TEST_GET_DOC_COMMENT' : "WP_CLI_TEST_GET_DOC_COMMENT=$_get_doc_comment" ); - putenv( false === $_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$_is_windows" ); + putenv( false === $get_doc_comment ? 'WP_CLI_TEST_GET_DOC_COMMENT' : "WP_CLI_TEST_GET_DOC_COMMENT=$get_doc_comment" ); + putenv( false === $is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$is_windows" ); } - public function testGetDocCommentWin(): void { + function testGetDocCommentWin() { // Save and set test env var. - $_get_doc_comment = getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ); - $_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); + $get_doc_comment = getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ); + $is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); putenv( 'WP_CLI_TEST_GET_DOC_COMMENT=1' ); putenv( 'WP_CLI_TEST_IS_WINDOWS=1' ); // Make private function accessible. $get_doc_comment = new \ReflectionMethod( 'WP_CLI\Dispatcher\CommandFactory', 'get_doc_comment' ); - if ( PHP_VERSION_ID < 80100 ) { - // @phpstan-ignore method.deprecated - $get_doc_comment->setAccessible( true ); - } + $get_doc_comment->setAccessible( true ); if ( ! class_exists( 'CommandFactoryTests_Get_Doc_Comment_1_Command', false ) ) { require __DIR__ . '/data/commandfactory-doc_comment-class.php'; @@ -289,157 +267,152 @@ public function testGetDocCommentWin(): void { require __DIR__ . '/data/commandfactory-doc_comment-class-win.php'; } - // Class 1. + // Class 1 - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 2. + // Class method 2 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command2' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 3. + // Class method 3 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command3' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 4. + // Class method 4 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command', 'command4' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Class 1 Windows. + // Class 1 Windows - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 2. + // Class method 2 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command2' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 3. + // Class method 3 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command3' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 4. + // Class method 4 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_1_Command_Win', 'command4' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Class 2. + // Class 2 - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Class 2 Windows. + // Class 2 Windows - // @phpstan-ignore argument.type $reflection = new \ReflectionClass( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Class method 1. + // Class method 1 $reflection = new \ReflectionMethod( 'CommandFactoryTests_Get_Doc_Comment_2_Command_Win', 'command1' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); $this->assertFalse( $actual ); - // Functions. + // Functions require __DIR__ . '/data/commandfactory-doc_comment-function-win.php'; - // Function 1 Windows. + // Function 1 Windows $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_1_win' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Function 2. + // Function 2 $reflection = new \ReflectionFunction( 'commandfactorytests_get_doc_comment_func_2_win' ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); - // Function 3. + // Function 3 - // @phpstan-ignore variable.undefined $reflection = new \ReflectionFunction( $commandfactorytests_get_doc_comment_func_3_win ); - $expected = $reflection->getDocComment(); + $expected = $reflection->getDocComment(); $actual = $get_doc_comment->invoke( null, $reflection ); $this->assertSame( $expected, $actual ); // Restore. - putenv( false === $_get_doc_comment ? 'WP_CLI_TEST_GET_DOC_COMMENT' : "WP_CLI_TEST_GET_DOC_COMMENT=$_get_doc_comment" ); - putenv( false === $_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$_is_windows" ); + putenv( false === $get_doc_comment ? 'WP_CLI_TEST_GET_DOC_COMMENT' : "WP_CLI_TEST_GET_DOC_COMMENT=$get_doc_comment" ); + putenv( false === $is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$is_windows" ); } } diff --git a/tests/test-configurator.php b/tests/test-configurator.php new file mode 100644 index 000000000..ea13aaeb3 --- /dev/null +++ b/tests/test-configurator.php @@ -0,0 +1,65 @@ +assertCount( 1, $args[0] ); + $this->assertCount( 2, $args[1] ); + + $this->assertEquals( 'foo', $args[0][0] ); + + $this->assertEquals( 'bar', $args[1][0][0] ); + $this->assertTrue( $args[1][0][1] ); + + $this->assertEquals( 'baz', $args[1][1][0] ); + $this->assertEquals( 'text', $args[1][1][1] ); + + } + + function testExtractAssocNoValue() { + $args = Configurator::extract_assoc( array( 'foo', '--bar=', '--baz=text' ) ); + + $this->assertCount( 1, $args[0] ); + $this->assertCount( 2, $args[1] ); + + $this->assertEquals( 'foo', $args[0][0] ); + + $this->assertEquals( 'bar', $args[1][0][0] ); + $this->assertEmpty( $args[1][0][1] ); + + $this->assertEquals( 'baz', $args[1][1][0] ); + $this->assertEquals( 'text', $args[1][1][1] ); + + } + + function testExtractAssocGlobalLocal() { + $args = Configurator::extract_assoc( array( '--url=foo.dev', '--path=wp', 'foo', '--bar=', '--baz=text', '--url=bar.dev' ) ); + + $this->assertCount( 1, $args[0] ); + $this->assertCount( 5, $args[1] ); + $this->assertCount( 2, $args[2] ); + $this->assertCount( 3, $args[3] ); + + $this->assertEquals( 'url', $args[2][0][0] ); + $this->assertEquals( 'foo.dev', $args[2][0][1] ); + $this->assertEquals( 'url', $args[3][2][0] ); + $this->assertEquals( 'bar.dev', $args[3][2][1] ); + } + + function testExtractAssocDoubleDashInValue() { + $args = Configurator::extract_assoc( array( '--test=text--text' ) ); + + $this->assertCount( 0, $args[0] ); + $this->assertCount( 1, $args[1] ); + + $this->assertEquals( 'test', $args[1][0][0] ); + $this->assertEquals( 'text--text', $args[1][0][1] ); + + } + + +} diff --git a/tests/DocParserTest.php b/tests/test-doc-parser.php similarity index 65% rename from tests/DocParserTest.php rename to tests/test-doc-parser.php index 9f0540a57..7fdf261c0 100644 --- a/tests/DocParserTest.php +++ b/tests/test-doc-parser.php @@ -1,22 +1,20 @@ assertEquals( '', $doc->get_shortdesc() ); $this->assertEquals( '', $doc->get_longdesc() ); $this->assertEquals( '', $doc->get_synopsis() ); - $this->assertEquals( '', $doc->get_tag( 'alias' ) ); + $this->assertEquals( '', $doc->get_tag('alias') ); } - public function test_only_tags(): void { - $doc = new DocParser( - <<<'EOB' + function test_only_tags() { + $doc = new DocParser( <<assertEquals( '', $doc->get_shortdesc() ); $this->assertEquals( '', $doc->get_longdesc() ); $this->assertEquals( '', $doc->get_synopsis() ); - $this->assertEquals( '', $doc->get_tag( 'foo' ) ); - $this->assertEquals( 'rock-on', $doc->get_tag( 'alias' ) ); - $this->assertEquals( 'revoke-md5-passwords', $doc->get_tag( 'subcommand' ) ); + $this->assertEquals( '', $doc->get_tag('foo') ); + $this->assertEquals( 'rock-on', $doc->get_tag('alias') ); + $this->assertEquals( 'revoke-md5-passwords', $doc->get_tag('subcommand') ); } - public function test_no_longdesc(): void { - $doc = new DocParser( - <<<'EOB' + function test_no_longdesc() { + $doc = new DocParser( <<assertEquals( 'Rock and roll!', $doc->get_shortdesc() ); $this->assertEquals( '', $doc->get_longdesc() ); $this->assertEquals( '', $doc->get_synopsis() ); - $this->assertEquals( 'rock-on', $doc->get_tag( 'alias' ) ); + $this->assertEquals( 'rock-on', $doc->get_tag('alias') ); } - public function test_complete(): void { - $doc = new DocParser( - <<<'EOB' + function test_complete() { + $doc = new DocParser( <<assertEquals( '[--volume=]', $doc->get_synopsis() ); $this->assertEquals( 'Start with one or more genres.', $doc->get_arg_desc( 'genre' ) ); $this->assertEquals( 'Sets the volume.', $doc->get_param_desc( 'volume' ) ); - $this->assertEquals( 'rock-on', $doc->get_tag( 'alias' ) ); + $this->assertEquals( 'rock-on', $doc->get_tag('alias') ); - $longdesc = <<<'EOB' + $longdesc = <<... @@ -96,12 +92,13 @@ public function test_complete(): void { ## EXAMPLES wp rock-on --volume=11 -EOB; +EOB + ; $this->assertEquals( $longdesc, $doc->get_longdesc() ); } - public function test_desc_parses_yaml(): void { - $longdesc = <<<'EOB' + public function test_desc_parses_yaml() { + $longdesc = <<assertEquals( 'Start with one or more genres.', $doc->get_arg_desc( 'genre' ) ); $this->assertEquals( 'Sets the volume.', $doc->get_param_desc( 'volume' ) ); - - $expected = [ - 'options' => [ 'rock', 'electronic' ], + $this->assertEquals( array( + 'options' => array( 'rock', 'electronic' ), 'default' => 'rock', - ]; - $this->assertEquals( $expected, $doc->get_arg_args( 'genre' ) ); - - $expected = [ + ), $doc->get_arg_args( 'genre' ) ); + $this->assertEquals( array( 'default' => 10, - ]; - $this->assertEquals( $expected, $doc->get_param_args( 'volume' ) ); - + ), $doc->get_param_args( 'volume' ) ); $this->assertNull( $doc->get_param_args( 'artist' ) ); } - public function test_desc_doesnt_parse_far_params_yaml(): void { - $longdesc = <<<'EOB' + public function test_desc_doesnt_parse_far_params_yaml() { + $longdesc = << @@ -171,19 +162,16 @@ public function test_desc_doesnt_parse_far_params_yaml(): void { - yaml --- EOB; - $doc = new DocParser( $longdesc ); - - $expected = [ + $this->assertEquals( array( 'default' => 'table', - 'options' => [ 'table', 'json', 'csv', 'yaml' ], - ]; - $this->assertEquals( $expected, $doc->get_param_args( 'format' ) ); + 'options' => array( 'table', 'json', 'csv', 'yaml' ), + ), $doc->get_param_args( 'format' ) ); $this->assertNull( $doc->get_arg_args( 'hook' ) ); } - public function test_desc_doesnt_parse_far_args_yaml(): void { - $longdesc = <<<'EOB' + public function test_desc_doesnt_parse_far_args_yaml() { + $longdesc = << @@ -200,14 +188,13 @@ public function test_desc_doesnt_parse_far_args_yaml(): void { - yaml --- EOB; - $doc = new DocParser( $longdesc ); - - $expected = [ + $this->assertEquals( array( 'default' => 'table', - 'options' => [ 'table', 'json', 'csv', 'yaml' ], - ]; - $this->assertEquals( $expected, $doc->get_arg_args( 'format' ) ); + 'options' => array( 'table', 'json', 'csv', 'yaml' ), + ), $doc->get_arg_args( 'format' ) ); $this->assertNull( $doc->get_arg_args( 'hook' ) ); } + } + diff --git a/tests/ExtractorTest.php b/tests/test-extractor.php similarity index 53% rename from tests/ExtractorTest.php rename to tests/test-extractor.php index 5150860aa..330b99390 100644 --- a/tests/ExtractorTest.php +++ b/tests/test-extractor.php @@ -1,15 +1,13 @@ setAccessible( true ); + self::$prev_logger = $class_wp_cli_logger->getValue(); - self::$logger = new Loggers\Execution(); + self::$logger = new \WP_CLI\Loggers\Execution; WP_CLI::set_logger( self::$logger ); // Remove any failed tests detritus. - $temp_dirs = glob( Utils\get_temp_dir() . self::$copy_overwrite_files_prefix . '*' ); - - $this->assertNotFalse( $temp_dirs ); - - foreach ( $temp_dirs as $temp_dir ) { + $temp_dirs = Utils\get_temp_dir() . self::$copy_overwrite_files_prefix . '*'; + foreach ( glob( $temp_dirs ) as $temp_dir ) { Extractor::rmdir( $temp_dir ); } } - public function tear_down(): void { + public function tearDown() { // Restore logger. WP_CLI::set_logger( self::$prev_logger ); - parent::tear_down(); + parent::tearDown(); } - public function test_rmdir(): void { + public function test_rmdir() { list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); $this->assertTrue( is_dir( $wp_dir ) ); @@ -63,7 +61,7 @@ public function test_rmdir(): void { $this->assertFalse( file_exists( $temp_dir ) ); } - public function test_err_rmdir(): void { + public function test_err_rmdir() { $msg = ''; try { Extractor::rmdir( 'no-such-dir' ); @@ -74,7 +72,7 @@ public function test_err_rmdir(): void { $this->assertTrue( empty( self::$logger->stderr ) ); } - public function test_copy_overwrite_files(): void { + public function test_copy_overwrite_files() { list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); $dest_dir = $temp_dir . '/dest'; @@ -90,7 +88,7 @@ public function test_copy_overwrite_files(): void { Extractor::rmdir( $temp_dir ); } - public function test_err_copy_overwrite_files(): void { + public function test_err_copy_overwrite_files() { $msg = ''; try { Extractor::copy_overwrite_files( 'no-such-dir', 'dest-dir' ); @@ -101,44 +99,24 @@ public function test_err_copy_overwrite_files(): void { $this->assertTrue( empty( self::$logger->stderr ) ); } - public function test_extract_tarball(): void { + public function test_extract_tarball() { if ( ! exec( 'tar --version' ) ) { $this->markTestSkipped( 'tar not installed.' ); } list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); - $tarball = $temp_dir . '/test.tar.gz'; + $tarball = $temp_dir . '/test.tar.gz'; $dest_dir = $temp_dir . '/dest'; // Create test tarball. - $output = []; + $output = array(); $return_var = -1; - // Need --force-local for Windows to avoid "C:" being interpreted as being on remote machine, and redirect for Mac as outputs verbosely on STDERR. - $cmd = 'tar czvf %1$s' . ( Utils\is_windows() ? ' --force-local' : '' ) . ' --directory=%2$s/src wordpress 2>&1'; - exec( Utils\esc_cmd( $cmd, $tarball, $temp_dir ), $output, $return_var ); + // Need --force-local for Windows to avoid "C:" being interpreted as being on remote machine. + exec( Utils\esc_cmd( 'tar czvf %1$s --force-local --directory=%2$s/src wordpress', $tarball, $temp_dir ), $output, $return_var ); $this->assertSame( 0, $return_var ); $this->assertFalse( empty( $output ) ); - - // Normalize (Mac) output. - $normalize = function ( $v ) { - if ( 'a ' === substr( $v, 0, 2 ) ) { - $v = substr( $v, 2 ); - } - if ( '/' !== substr( $v, -1 ) && false === strpos( $v, '.' ) ) { - $v .= '/'; - } - return $v; - }; - $output = array_filter( - $output, - function ( $v ) { - return 0 !== strpos( basename( $v ), '._' ); - } - ); - $output = array_map( $normalize, $output ); sort( $output ); - $this->assertSame( self::recursive_scandir( $src_dir ), $output ); // Test. @@ -152,57 +130,7 @@ function ( $v ) { Extractor::rmdir( $temp_dir ); } - public function test_extract_tarball_fallback_to_phardata(): void { - if ( Utils\is_windows() ) { - $this->markTestSkipped( 'Hiding system tar via PATH is not supported on Windows.' ); - } - - if ( ! class_exists( 'PharData' ) ) { - $this->markTestSkipped( 'PharData not available.' ); - } - - $msg = ''; - - list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); - - $tarball = $temp_dir . '/test.tar.gz'; - $dest_dir = $temp_dir . '/dest'; - - // Create test tarball using PharData. - $phar = new \PharData( $tarball ); - $phar->buildFromDirectory( $src_dir ); - - // Temporarily hide system 'tar' by overriding PATH. - $prev_path = getenv( 'PATH' ); - $prev_env_path = isset( $_ENV['PATH'] ) ? $_ENV['PATH'] : null; - - putenv( 'PATH=/does/not/exist' ); - $_ENV['PATH'] = '/does/not/exist'; - - try { - // Test. - Extractor::extract( $tarball, $dest_dir ); - } catch ( \Throwable $e ) { - $msg = $e->getMessage(); - } finally { - // Restore environment. - putenv( false === $prev_path ? 'PATH' : "PATH=$prev_path" ); - if ( null === $prev_env_path ) { - unset( $_ENV['PATH'] ); - } else { - $_ENV['PATH'] = $prev_env_path; - } - } - - $files = self::recursive_scandir( $dest_dir ); - // Clean up. - Extractor::rmdir( $temp_dir ); - $this->assertSame( self::$expected_wp, $files ); - $this->assertTrue( 0 === strpos( self::$logger->stderr, 'Warning: tar xz failed, falling back to PharData' ) ); - $this->assertEmpty( $msg ); - } - - public function test_err_extract_tarball(): void { + public function test_err_extract_tarball() { // Non-existent. $msg = ''; try { @@ -210,13 +138,11 @@ public function test_err_extract_tarball(): void { } catch ( \Exception $e ) { $msg = $e->getMessage(); } - $this->assertTrue( false !== strpos( $msg, 'no-such-tar' ) ); - $this->assertTrue( empty( self::$logger->stderr ) ); + $this->assertTrue( 0 === strpos( self::$logger->stderr, 'Warning: PharData failed' ) ); + $this->assertTrue( false !== strpos( self::$logger->stderr, 'no-such-tar' ) ); - // Reset logger. - self::$logger->stderr = ''; - self::$logger->stdout = ''; + self::$logger->stderr = self::$logger->stdout = ''; // Reset logger. // Zero-length. $zero_tar = Utils\get_temp_dir() . 'zero-tar.tar.gz'; @@ -228,44 +154,23 @@ public function test_err_extract_tarball(): void { $msg = $e->getMessage(); } unlink( $zero_tar ); - $this->assertTrue( false !== strpos( $msg, 'zero-tar' ) ); - $this->assertTrue( empty( self::$logger->stderr ) ); + $this->assertTrue( 0 === strpos( self::$logger->stderr, 'Warning: PharData failed' ) ); + $this->assertTrue( false !== strpos( self::$logger->stderr, 'zero-tar' ) ); } - public function test_extract_tarball_both_failed(): void { - $invalid_tar = Utils\get_temp_dir() . 'invalid-tar.tar.gz'; - file_put_contents( $invalid_tar, 'invalid tar content' ); - - $msg = ''; - try { - Extractor::extract( $invalid_tar, 'dest-dir' ); - } catch ( \Exception $e ) { - $msg = $e->getMessage(); - } - unlink( $invalid_tar ); - - $this->assertTrue( false !== strpos( $msg, 'Failed to extract the tarball.' ) ); - $this->assertTrue( false !== strpos( $msg, 'tar xz failed:' ) ); - if ( class_exists( 'PharData' ) ) { - $this->assertTrue( false !== strpos( $msg, 'PharData failed:' ) ); - } else { - $this->assertFalse( strpos( $msg, 'PharData failed:' ) ); - } - } - - public function test_extract_zip(): void { + public function test_extract_zip() { if ( ! class_exists( 'ZipArchive' ) ) { $this->markTestSkipped( 'ZipArchive not installed.' ); } list( $temp_dir, $src_dir, $wp_dir ) = self::create_test_directory_structure(); - $zipfile = $temp_dir . '/test.zip'; + $zipfile = $temp_dir . '/test.zip'; $dest_dir = $temp_dir . '/dest'; // Create test zip. - $zip = new ZipArchive(); + $zip = new ZipArchive; $result = $zip->open( $zipfile, ZipArchive::CREATE ); $this->assertTrue( $result ); $files = self::recursive_scandir( $src_dir ); @@ -291,7 +196,7 @@ public function test_extract_zip(): void { Extractor::rmdir( $temp_dir ); } - public function test_err_extract_zip(): void { + public function test_err_extract_zip() { if ( ! class_exists( 'ZipArchive' ) ) { $this->markTestSkipped( 'ZipArchive not installed.' ); } @@ -306,9 +211,7 @@ public function test_err_extract_zip(): void { $this->assertTrue( false !== strpos( $msg, 'no-such-zip' ) ); $this->assertTrue( empty( self::$logger->stderr ) ); - // Reset logger. - self::$logger->stderr = ''; - self::$logger->stdout = ''; + self::$logger->stderr = self::$logger->stdout = ''; // Reset logger. // Zero-length. $zero_zip = Utils\get_temp_dir() . 'zero-zip.zip'; @@ -324,7 +227,7 @@ public function test_err_extract_zip(): void { $this->assertTrue( empty( self::$logger->stderr ) ); } - public function test_err_extract(): void { + public function test_err_extract() { $msg = ''; try { Extractor::extract( 'not-supported.tar.xz', 'dest-dir' ); @@ -335,7 +238,7 @@ public function test_err_extract(): void { $this->assertTrue( empty( self::$logger->stderr ) ); } - private static function create_test_directory_structure() { + private function create_test_directory_structure() { $temp_dir = Utils\get_temp_dir() . uniqid( self::$copy_overwrite_files_prefix, true ); mkdir( $temp_dir ); @@ -353,27 +256,17 @@ private static function create_test_directory_structure() { } } - return [ $temp_dir, $src_dir, $wp_dir ]; + return array( $temp_dir, $src_dir, $wp_dir ); } - private static function recursive_scandir( $dir, $prefix_dir = '' ) { - $dirs = scandir( $dir ); - if ( ! $dirs ) { - return []; - } - - $ret = []; - - foreach ( array_diff( $dirs, [ '.', '..' ] ) as $file ) { - if ( 0 === strpos( $file, '._' ) ) { - continue; - } - + private function recursive_scandir( $dir, $prefix_dir = '' ) { + $ret = array(); + foreach ( array_diff( scandir( $dir ), array( '.', '..' ) ) as $file ) { if ( is_dir( $dir . '/' . $file ) ) { - $ret[] = ( $prefix_dir ? ( $prefix_dir . '/' . $file ) : $file ) . '/'; - $ret = array_merge( $ret, self::recursive_scandir( $dir . '/' . $file, $prefix_dir ? ( $prefix_dir . '/' . $file ) : $file ) ); + $ret[] = ( $prefix_dir ? ( $prefix_dir . '/'. $file ) : $file ) . '/'; + $ret = array_merge( $ret, self::recursive_scandir( $dir . '/' . $file, $prefix_dir ? ( $prefix_dir . '/' . $file ) : $file ) ); } else { - $ret[] = $prefix_dir ? ( $prefix_dir . '/' . $file ) : $file; + $ret[] = $prefix_dir ? ( $prefix_dir . '/'. $file ) : $file; } } return $ret; diff --git a/tests/test-file-cache.php b/tests/test-file-cache.php new file mode 100644 index 000000000..1ab58e6fb --- /dev/null +++ b/tests/test-file-cache.php @@ -0,0 +1,84 @@ +assertSame( $cache_dir . '/', $cache->get_root() ); + unset( $cache ); + + $cache = new FileCache( $cache_dir . '/', $ttl, $max_size ); + $this->assertSame( $cache_dir . '/', $cache->get_root() ); + unset( $cache ); + + $cache = new FileCache( $cache_dir . '\\', $ttl, $max_size ); + $this->assertSame( $cache_dir . '/', $cache->get_root() ); + unset( $cache ); + + rmdir( $cache_dir ); + } + + public function test_ensure_dir_exists() { + $class_wp_cli_logger = new ReflectionProperty( 'WP_CLI', 'logger' ); + $class_wp_cli_logger->setAccessible( true ); + $prev_logger = $class_wp_cli_logger->getValue(); + + $logger = new WP_CLI\Loggers\Execution; + WP_CLI::set_logger( $logger ); + + $max_size = 32; + $ttl = 60; + $cache_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-file-cache', true ); + + $cache = new FileCache( $cache_dir, $ttl, $max_size ); + $test_class = new ReflectionClass( $cache ); + $method = $test_class->getMethod( 'ensure_dir_exists' ); + $method->setAccessible( true ); + + // Cache directory should be created. + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test1' ) ); + $this->assertTrue( $result ); + $this->assertTrue( is_dir( $cache_dir . '/test1' ) ); + + // Try to create the same directory again. it should return true. + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test1' ) ); + $this->assertTrue( $result ); + + // `chmod()` doesn't work on Windows. + if ( ! Utils\is_windows() ) { + // It should be failed because permission denied. + $logger->stderr = ''; + chmod( $cache_dir . '/test1', 0000 ); + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test1/error' ) ); + $expected = "/^Warning: Failed to create directory '.+': mkdir\(\): Permission denied\.$/"; + $this->assertRegexp( $expected, $logger->stderr ); + } + + // It should be failed because file exists. + $logger->stderr = ''; + file_put_contents( $cache_dir . '/test2', '' ); + $result = $method->invokeArgs( $cache, array( $cache_dir . '/test2' ) ); + $expected = "/^Warning: Failed to create directory '.+': mkdir\(\): File exists\.$/"; + $this->assertRegexp( $expected, $logger->stderr ); + + // Restore + chmod( $cache_dir . '/test1', 0755 ); + rmdir( $cache_dir . '/test1' ); + unlink( $cache_dir . '/test2' ); + rmdir( $cache_dir ); + $class_wp_cli_logger->setValue( $prev_logger ); + } +} diff --git a/tests/test-help.php b/tests/test-help.php new file mode 100644 index 000000000..9cbbb2a62 --- /dev/null +++ b/tests/test-help.php @@ -0,0 +1,117 @@ +getMethod( 'parse_reference_links' ); + $method->setAccessible( true ); + + $desc = 'This is a [reference link](https://wordpress.org/). It should be displayed very nice!'; + $result = $method->invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc = 'This is a [reference link](https://wordpress.org/) and [second link](http://wp-cli.org/). It should be displayed very nice!'; + $result = $method->invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + + $desc =<<invokeArgs( null, array( $desc ) ); + + $expected =<<assertSame( $expected, $result ); + } +} diff --git a/tests/test-logging.php b/tests/test-logging.php new file mode 100644 index 000000000..8ab356707 --- /dev/null +++ b/tests/test-logging.php @@ -0,0 +1,113 @@ + array ( + 'debug' => true + ) + ); + } + + protected function write( $handle, $str ) { + echo $str; + } +} + +class MockQuietLogger extends WP_CLI\Loggers\Quiet { + + protected function get_runner() { + return (object) array ( + 'config' => array ( + 'debug' => true + ) + ); + } +} + +class LoggingTests extends PHPUnit_Framework_TestCase { + + function testLogDebug() { + $message = 'This is a test message.'; + + $regularLogger = new MockRegularLogger( false ); + $this->expectOutputRegex( "/Debug: {$message} \(\d+\.*\d*s\)/" ); + $regularLogger->debug( $message ); + + $quietLogger = new MockQuietLogger(); + $this->expectOutputRegex( "/Debug: {$message} \(\d+\.*\d*s\)/" ); + $quietLogger->debug( $message ); + } + + function testLogEscaping() { + $logger = new MockRegularLogger( false ); + + $message = 'foo%20bar'; + + $this->expectOutputString( "Success: $message\n" ); + $logger->success( $message ); + } + + function testExecutionLogger() { + // Save Runner config. + $runner = WP_CLI::get_runner(); + $runner_config = new \ReflectionProperty( $runner, 'config' ); + $runner_config->setAccessible( true ); + + $prev_config = $runner_config->getValue( $runner ); + + // Set debug. + $runner_config->setValue( $runner, array( 'debug' => true ) ); + + $logger = new WP_CLI\Loggers\Execution; + + // Standard use. + + $logger->info( 'info' ); + $logger->info( 'info2' ); + $logger->success( 'success' ); + $logger->warning( 'warning' ); + $logger->error( 'error' ); + $logger->success( 'success2' ); + $logger->warning( 'warning2' ); + $logger->debug( 'debug', 'group' ); + $logger->error_multi_line( array( "line11", "line12", "line13" ) ); + $logger->error( 'error2' ); + $logger->error_multi_line( array( "line21" ) ); + $logger->debug( 'debug2', 'group2' ); + + $this->assertSame( "info\ninfo2\nSuccess: success\nSuccess: success2\n", $logger->stdout ); + $this->assertSame( 1, preg_match( '/^' + . 'Warning: warning\nError: error\n' + . 'Warning: warning2\nDebug \(group\): debug \([0-9.]+s\)\n' + . 'Error:\nline11\nline12\nline13\n---------\n\nError: error2\n' + . 'Error:\nline21\n---------\n\nDebug \(group2\): debug2 \([0-9.]+s\)$/', $logger->stderr ) ); + + $logger->stdout = $logger->stderr = ''; + + // With output buffering. + + $logger->ob_start(); + + echo "echo"; + $logger->info( 'info' ); + print "print\n"; + $logger->success( 'success' ); + echo "echo2\n"; + $logger->error( 'error' ); + echo "echo3\n"; + $logger->success( 'success2' ); + echo "echo4"; + + $logger->ob_end(); + + $this->assertSame( "echoinfo\nprint\nSuccess: success\necho2\necho3\nSuccess: success2\necho4", $logger->stdout ); + $this->assertSame( "Error: error\n", $logger->stderr ); + + $logger->stdout = $logger->stderr = ''; + + // Restore. + $runner_config->setValue( $runner, $prev_config ); + } +} diff --git a/tests/test-process.php b/tests/test-process.php new file mode 100644 index 000000000..562451597 --- /dev/null +++ b/tests/test-process.php @@ -0,0 +1,29 @@ +run(); + + $this->assertSame( $process_run->stdout, $expected_out ); + } + + function data_process_env() { + return array( + array( '', array(), array(), '' ), + array( 'ENV=blah', array(), array( 'ENV' ), 'blah' ), + array( 'ENV="blah blah"', array(), array( 'ENV' ), 'blah blah' ), + array( 'ENV_1="blah1 blah1" ENV_2="blah2" ENV_3=blah3', array( 'ENV' => 'in' ), array( 'ENV', 'ENV_1', 'ENV_2', 'ENV_3' ), 'inblah1 blah1blah2blah3' ), + array( 'ENV=blah', array( 'ENV_1' => 'in1', 'ENV_2' => 'in2' ), array( 'ENV_1', 'ENV_2', 'ENV' ), 'in1in2blah' ), + ); + } +} diff --git a/tests/test-search-replace.php b/tests/test-search-replace.php new file mode 100644 index 000000000..c08a8a475 --- /dev/null +++ b/tests/test-search-replace.php @@ -0,0 +1,86 @@ +run( $data, $serialised ); + } + + function testReplaceString() { + $orig = 'foo'; + $replacement = self::recursive_unserialize_replace( 'foo', 'bar', $orig ); + $this->assertEquals( 'bar', $replacement ); + } + + function testPrivateConstructor() { + $old_obj = ClassWithPrivateConstructor::get_instance(); + + $new_obj = self::recursive_unserialize_replace( 'foo', 'bar', $old_obj, false, true ); + $this->assertEquals( 'bar', $new_obj->prop ); + } + + function testObjectLoop() { + $old_object = new stdClass(); + $old_object->prop = 'foo'; + $old_object->self = $old_object; + + $new_obj = self::recursive_unserialize_replace( 'foo', 'bar', $old_object, false, true ); + $this->assertEquals( 'bar', $new_obj->prop ); + } + + function testArrayLoop() { + $old_array = array( 'prop' => 'foo' ); + $old_array['self'] = &$old_array; + + $new_array = self::recursive_unserialize_replace( 'foo', 'bar', $old_array, false, true ); + $this->assertEquals( 'bar', $new_array['prop'] ); + } + + function testArraySameValues() { + $old_array = array( + 'prop1' => array( + 'foo', + ), + 'prop2' => array( + 'foo', + ), + ); + $new_array = self::recursive_unserialize_replace( 'foo', 'bar', $old_array, false, true ); + $this->assertEquals( 'bar', $new_array['prop1'][0] ); + $this->assertEquals( 'bar', $new_array['prop2'][0] ); + } + + function testMixedObjectArrayLoop() { + $old_object = new stdClass(); + $old_object->prop = 'foo'; + $old_object->array = array('prop' => 'foo'); + $old_object->array['object'] = $old_object; + + $new_object = self::recursive_unserialize_replace( 'foo', 'bar', $old_object, false, true ); + $this->assertEquals( 'bar', $new_object->prop ); + $this->assertEquals( 'bar', $new_object->array['prop']); + } + + function testMixedArrayObjectLoop() { + $old_array = array( 'prop' => 'foo', 'object' => new stdClass() ); + $old_array['object']->prop = 'foo'; + $old_array['object']->array = &$old_array; + + $new_array = self::recursive_unserialize_replace( 'foo', 'bar', $old_array, false, true ); + $this->assertEquals( 'bar', $new_array['prop'] ); + $this->assertEquals( 'bar', $new_array['object']->prop); + } +} + + +class ClassWithPrivateConstructor { + + public $prop = 'foo'; + + private function __construct() {} + + public static function get_instance() { + return new self; + } +} diff --git a/tests/SynopsisParserTest.php b/tests/test-synopsis.php similarity index 74% rename from tests/SynopsisParserTest.php rename to tests/test-synopsis.php index 2e2c6ae4f..418d74d81 100644 --- a/tests/SynopsisParserTest.php +++ b/tests/test-synopsis.php @@ -1,25 +1,16 @@ assertEmpty( $r ); } - public function testPositional(): void { + function testPositional() { $r = SynopsisParser::parse( ' []' ); $this->assertCount( 2, $r ); @@ -33,7 +24,7 @@ public function testPositional(): void { $this->assertTrue( $param['optional'] ); } - public function testFlag(): void { + function testFlag() { $r = SynopsisParser::parse( '[--foo]' ); $this->assertCount( 1, $r ); @@ -42,7 +33,7 @@ public function testFlag(): void { $this->assertEquals( 'flag', $param['type'] ); $this->assertTrue( $param['optional'] ); - // Flags can't be mandatory. + // flags can't be mandatory $r = SynopsisParser::parse( '--foo' ); $this->assertCount( 1, $r ); @@ -51,7 +42,7 @@ public function testFlag(): void { $this->assertEquals( 'unknown', $param['type'] ); } - public function testGeneric(): void { + function testGeneric() { $r = SynopsisParser::parse( '--= [--=] --[=] [--[=]]' ); $this->assertCount( 4, $r ); @@ -71,7 +62,7 @@ public function testGeneric(): void { $this->assertEquals( 'unknown', $param['type'] ); } - public function testAssoc(): void { + function testAssoc() { $r = SynopsisParser::parse( '--foo= [--bar=] [--bar[=]]' ); $this->assertCount( 3, $r ); @@ -90,7 +81,7 @@ public function testAssoc(): void { $this->assertTrue( $param['value']['optional'] ); } - public function testInvalidAssoc(): void { + function testInvalidAssoc() { $r = SynopsisParser::parse( '--bar[=] --bar=[] --count=100' ); $this->assertCount( 3, $r ); @@ -100,7 +91,7 @@ public function testInvalidAssoc(): void { $this->assertEquals( 'unknown', $r[2]['type'] ); } - public function testRepeating(): void { + function testRepeating() { $r = SynopsisParser::parse( '... [--=...]' ); $this->assertCount( 2, $r ); @@ -114,7 +105,7 @@ public function testRepeating(): void { $this->assertTrue( $param['repeating'] ); } - public function testCombined(): void { + function testCombined() { $r = SynopsisParser::parse( ' --assoc= --= [--flag]' ); $this->assertCount( 4, $r ); @@ -125,7 +116,7 @@ public function testCombined(): void { $this->assertEquals( 'flag', $r[3]['type'] ); } - public function testAllowedValueCharacters(): void { + function testAllowedValueCharacters() { $r = SynopsisParser::parse( '--capitals= --hyphen= --combined= --disallowed=' ); $this->assertCount( 4, $r ); @@ -145,55 +136,43 @@ public function testAllowedValueCharacters(): void { $this->assertEquals( 'unknown', $r[3]['type'] ); } - public function testRender(): void { - /** - * @phpstan-var array{0: PositionalParameter, 1: PositionalParameter, 2: AssocParameter, 3: AssocParameter, 4: AssocParameter} $a - */ - $a = [ - [ + function testRender() { + $a = array( + array( 'name' => 'message', 'type' => 'positional', 'description' => 'A short message to display to the user.', - ], - [ + ), + array( 'name' => 'secrets', 'type' => 'positional', 'description' => 'You may tell secrets, or you may not', 'optional' => true, 'repeating' => true, - ], - [ + ), + array( 'name' => 'meal', 'type' => 'assoc', 'description' => 'A meal during the day or night.', - ], - [ + ), + array( 'name' => 'snack', 'type' => 'assoc', 'description' => 'If you are hungry between meals, you should snack.', 'optional' => true, - ], - [ - 'name' => 'skip', - 'type' => 'assoc', - 'description' => 'Skip all meals, or skip a single meal by name.', - 'optional' => true, - 'value' => [ - 'optional' => true, - ], - ], - ]; - $this->assertEquals( ' [...] --meal= [--snack=] [--skip[=]]', SynopsisParser::render( $a ) ); + ) + ); + $this->assertEquals( ' [...] --meal= [--snack=]', SynopsisParser::render( $a ) ); } - public function testParseThenRender(): void { - $o = ' --assoc= [--double[=]] --= [--flag]'; + function testParseThenRender() { + $o = ' --assoc= --= [--flag]'; $a = SynopsisParser::parse( $o ); $r = SynopsisParser::render( $a ); $this->assertEquals( $o, $r ); } - public function testParseThenRenderNumeric(): void { + function testParseThenRenderNumeric() { $o = ' --a2ssoc= --= [--f3lag]'; $a = SynopsisParser::parse( $o ); $this->assertEquals( 'p1ositional', $a[0]['name'] ); @@ -202,4 +181,5 @@ public function testParseThenRenderNumeric(): void { $r = SynopsisParser::render( $a ); $this->assertEquals( $o, $r ); } + } diff --git a/tests/test-utils.php b/tests/test-utils.php new file mode 100644 index 000000000..52d7cf4f6 --- /dev/null +++ b/tests/test-utils.php @@ -0,0 +1,725 @@ +assertEquals( + Utils\increment_version( '1.2.3-pre', 'same' ), + '1.2.3-pre' + ); + + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', 'patch' ), + '1.2.4' + ); + + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', 'minor' ), + '1.3.0' + ); + + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', 'major' ), + '2.0.0' + ); + + // custom version string + $this->assertEquals( + Utils\increment_version( '1.2.3-pre', '4.5.6-alpha1' ), + '4.5.6-alpha1' + ); + } + + public function testGetSemVer() { + $original_version = '0.19.1'; + $this->assertEmpty( Utils\get_named_sem_ver( '0.18.0', $original_version ) ); + $this->assertEmpty( Utils\get_named_sem_ver( '0.19.1', $original_version ) ); + $this->assertEquals( 'patch', Utils\get_named_sem_ver( '0.19.2', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '0.20.0', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '0.20.3', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '1.0.0', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '1.1.1', $original_version ) ); + } + + public function testGetSemVerWP() { + $original_version = '3.0'; + $this->assertEmpty( Utils\get_named_sem_ver( '2.8', $original_version ) ); + $this->assertEmpty( Utils\get_named_sem_ver( '2.9.1', $original_version ) ); + $this->assertEquals( 'patch', Utils\get_named_sem_ver( '3.0.1', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1', $original_version ) ); + $this->assertEquals( 'minor', Utils\get_named_sem_ver( '3.1.1', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '4.0', $original_version ) ); + $this->assertEquals( 'major', Utils\get_named_sem_ver( '4.1.1', $original_version ) ); + } + + public function testParseSSHUrl() { + $testcase = 'foo'; + $this->assertEquals( array( + 'host' => 'foo', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com'; + $this->assertEquals( array( + 'host' => 'foo.com', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com:2222'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'port' => 2222, + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( 2222, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com:2222/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'port' => 2222, + 'path' => '/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( 2222, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com~/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // No host + $testcase = '~/path/to/dir'; + $this->assertEquals( array(), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // host and path, no port, with scp notation + $testcase = 'foo.com:~/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + $testcase = 'foo.com:2222~/path/to/dir'; + $this->assertEquals( array( + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + 'port' => '2222' + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( '2222', Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // explicit scheme, user, host, path, no port + $testcase = 'ssh:bar@foo.com:~/path/to/dir'; + $this->assertEquals( array( + 'scheme' => 'ssh', + 'user' => 'bar', + 'host' => 'foo.com', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'ssh', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'foo.com', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // container scheme + $testcase = 'docker:wordpress'; + $this->assertEquals( array( + 'scheme' => 'docker', + 'host' => 'wordpress', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'docker', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // container scheme with user, and host + $testcase = 'docker:bar@wordpress'; + $this->assertEquals( array( + 'scheme' => 'docker', + 'user' => 'bar', + 'host' => 'wordpress', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'docker', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // container scheme with user, host, and path + $testcase = 'docker-compose:bar@wordpress:~/path/to/dir'; + $this->assertEquals( array( + 'scheme' => 'docker-compose', + 'user' => 'bar', + 'host' => 'wordpress', + 'path' => '~/path/to/dir', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'docker-compose', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( 'bar', Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'wordpress', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '~/path/to/dir', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // vagrant scheme + $testcase = 'vagrant:default'; + $this->assertEquals( array( + 'scheme' => 'vagrant', + 'host' => 'default', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( 'vagrant', Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'default', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // vagrant scheme + $testcase = 'vagrant:/var/www/html'; + $this->assertEquals( array( + 'host' => 'vagrant', + 'path' => '/var/www/html', + ), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( 'vagrant', Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( '/var/www/html', Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + + // unsupported scheme, should not match + $testcase = 'foo:bar'; + $this->assertEquals( array(), Utils\parse_ssh_url( $testcase ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_SCHEME ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_USER ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_HOST ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PORT ) ); + $this->assertEquals( null, Utils\parse_ssh_url( $testcase, PHP_URL_PATH ) ); + } + + public function testParseStrToArgv() { + $this->assertEquals( array(), Utils\parse_str_to_argv( '' ) ); + $this->assertEquals( array( + 'option', + 'get', + 'home', + ), Utils\parse_str_to_argv( 'option get home' ) ); + $this->assertEquals( array( + 'core', + 'download', + '--path=/var/www/', + ), Utils\parse_str_to_argv( 'core download --path=/var/www/' ) ); + $this->assertEquals( array( + 'eval', + 'echo wp_get_current_user()->user_login;', + ), Utils\parse_str_to_argv( 'eval "echo wp_get_current_user()->user_login;"' ) ); + } + + public function testAssocArgsToString() { + // Strip quotes for Windows compat. + $strip_quotes = function ( $str ) { + return str_replace( array( '"', "'" ), '', $str ); + }; + + $expected = " --url='foo.dev' --porcelain --apple='banana'"; + $actual = Utils\assoc_args_to_str( array( + 'url' => 'foo.dev', + 'porcelain' => true, + 'apple' => 'banana' + ) ); + $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); + + $expected = " --url='foo.dev' --require='file-a.php' --require='file-b.php' --porcelain --apple='banana'"; + $actual = Utils\assoc_args_to_str( array( + 'url' => 'foo.dev', + 'require' => array( + 'file-a.php', + 'file-b.php', + ), + 'porcelain' => true, + 'apple' => 'banana' + ) ); + $this->assertSame( $strip_quotes( $expected ), $strip_quotes( $actual ) ); + } + + public function testForceEnvOnNixSystems() { + $env_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); + + putenv( 'WP_CLI_TEST_IS_WINDOWS=0' ); + $this->assertSame( '/usr/bin/env cmd', Utils\force_env_on_nix_systems( 'cmd' ) ); + $this->assertSame( '/usr/bin/env cmd', Utils\force_env_on_nix_systems( '/usr/bin/env cmd' ) ); + + putenv( 'WP_CLI_TEST_IS_WINDOWS=1' ); + $this->assertSame( 'cmd', Utils\force_env_on_nix_systems( 'cmd' ) ); + $this->assertSame( 'cmd', Utils\force_env_on_nix_systems( '/usr/bin/env cmd' ) ); + + putenv( false === $env_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$env_is_windows" ); + } + + public function testGetHomeDir() { + + // save environments + $home = getenv( 'HOME' ); + $homedrive = getenv( 'HOMEDRIVE' ); + $homepath = getenv( 'HOMEPATH' ); + + putenv( 'HOME=/home/user' ); + $this->assertSame('/home/user', Utils\get_home_dir() ); + + putenv( 'HOME' ); + + putenv( 'HOMEDRIVE=D:' ); + putenv( 'HOMEPATH' ); + $this->assertSame( 'D:', Utils\get_home_dir() ); + + putenv( 'HOMEPATH=\\Windows\\User\\' ); + $this->assertSame( 'D:\\Windows\\User', Utils\get_home_dir() ); + + putenv( 'HOMEPATH=\\Windows\\User\\HOGE\\' ); + $this->assertSame( 'D:\\Windows\\User\\HOGE', Utils\get_home_dir() ); + + // restore environments + putenv( false === $home ? 'HOME' : "HOME=$home" ); + putenv( false === $homedrive ? 'HOMEDRIVE' : "HOME=$homedrive" ); + putenv( false === $homepath ? 'HOMEPATH' : "HOME=$homepath" ); + } + + public function testTrailingslashit() { + $this->assertSame( 'a/', Utils\trailingslashit( 'a' ) ); + $this->assertSame( 'a/', Utils\trailingslashit( 'a/' ) ); + $this->assertSame( 'a/', Utils\trailingslashit( 'a\\' ) ); + $this->assertSame( 'a/', Utils\trailingslashit( 'a\\//\\' ) ); + } + + public function testGetTempDir() { + $this->assertTrue( '/' === substr( Utils\get_temp_dir(), -1 ) ); + + // INI directive `sys_temp_dir` introduced PHP 5.5.0. + if ( version_compare( PHP_VERSION, '5.5.0', '>=' ) ) { + + // `sys_temp_dir` set to unwritable. + + $cmd = 'php ' . escapeshellarg( '-dsys_temp_dir=\\tmp\\' ) . ' php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'echo WP_CLI\\Utils\\get_temp_dir();' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, 'Warning' ) ); + $this->assertTrue( false !== strpos( $output, 'writable' ) ); + $this->assertTrue( false !== strpos( $output, '\\tmp/' ) ); + + // `sys_temp_dir` unset. + + $cmd = 'php ' . escapeshellarg( '-dsys_temp_dir=' ) . ' php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'echo WP_CLI\\Utils\\get_temp_dir();' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( '/' === substr( $output, -1 ) ); + } + } + + public function testHttpRequestBadAddress() { + // Save WP_CLI state. + $class_wp_cli_logger = new \ReflectionProperty( 'WP_CLI', 'logger' ); + $class_wp_cli_logger->setAccessible( true ); + $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); + $class_wp_cli_capture_exit->setAccessible( true ); + + $prev_logger = $class_wp_cli_logger->getValue(); + $prev_capture_exit = $class_wp_cli_capture_exit->getValue(); + + // Enable exit exception. + $class_wp_cli_capture_exit->setValue( true ); + + $logger = new \WP_CLI\Loggers\Execution; + WP_CLI::set_logger( $logger ); + + $exception = null; + try { + Utils\http_request( 'GET', 'https://nosuchhost_asdf_asdf_asdf.com', null /*data*/, array() /*headers*/, array( 'timeout' => 0.01 ) ); + } catch ( \WP_CLI\ExitException $ex ) { + $exception = $ex; + } + $this->assertTrue( null !== $exception ); + $this->assertTrue( 1 === $exception->getCode() ); + $this->assertTrue( empty( $logger->stdout ) ); + $this->assertTrue( false === strpos( $logger->stderr, 'Warning' ) ); + $this->assertTrue( 0 === strpos( $logger->stderr, 'Error: Failed to get url' ) ); + + // Restore. + $class_wp_cli_logger->setValue( $prev_logger ); + $class_wp_cli_capture_exit->setValue( $prev_capture_exit ); + } + + public function testHttpRequestBadCAcert() { + if ( ! extension_loaded( 'curl' ) ) { + $this->markTestSkipped( 'curl not available' ); + } + + // Save WP_CLI state. + $class_wp_cli_logger = new \ReflectionProperty( 'WP_CLI', 'logger' ); + $class_wp_cli_logger->setAccessible( true ); + + $prev_logger = $class_wp_cli_logger->getValue(); + + $have_bad_cacert = false; + $created_dirs = array(); + + // Hack to create bad CAcert, using Utils\get_vendor_paths() preference for a path as part of a Composer-installed larger project. + $vendor_dir = WP_CLI_ROOT . '/../../../vendor'; + $cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; + $bad_cacert_path = $vendor_dir . $cert_path; + if ( ! file_exists( $bad_cacert_path ) ) { + // Capture any directories created so can clean up. + $dirs = array_merge( array( 'vendor' ), array_filter( explode( '/', dirname( $cert_path ) ) ) ); + $current_dir = dirname( $vendor_dir ); + foreach ( $dirs as $dir ) { + if ( ! file_exists( $current_dir . '/' . $dir ) ) { + if ( ! @mkdir( $current_dir . '/' . $dir ) ) { + break; + } + $created_dirs[] = $current_dir . '/' . $dir; + } + $current_dir .= '/' . $dir; + } + if ( $current_dir === dirname( $bad_cacert_path ) && file_put_contents( $bad_cacert_path, "-----BEGIN CERTIFICATE-----\nasdfasdf\n-----END CERTIFICATE-----\n" ) ) { + $have_bad_cacert = true; + } + } + + if ( ! $have_bad_cacert ) { + foreach ( array_reverse( $created_dirs ) as $created_dir ) { + rmdir( $created_dir ); + } + $this->markTestSkipped( 'Unable to create bad CAcert.' ); + } + + $logger = new \WP_CLI\Loggers\Execution; + WP_CLI::set_logger( $logger ); + + Utils\http_request( 'GET', 'https://example.com' ); + + // Undo bad CAcert hack before asserting. + unlink( $bad_cacert_path ); + foreach ( array_reverse( $created_dirs ) as $created_dir ) { + rmdir( $created_dir ); + } + + $this->assertTrue( empty( $logger->stdout ) ); + $this->assertTrue( 0 === strpos( $logger->stderr, 'Warning: Re-trying without verify after failing to get verified url' ) ); + $this->assertFalse( strpos( $logger->stderr, 'Error' ) ); + + // Restore. + $class_wp_cli_logger->setValue( $prev_logger ); + } + + public function testRunMysqlCommandProcDisabled() { + $err_msg = 'Error: Cannot do \'run_mysql_command\': The PHP functions `proc_open()` and/or `proc_close()` are disabled'; + + $cmd = 'php -ddisable_functions=proc_open php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'WP_CLI\\Utils\\run_mysql_command( null, array() );' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, $err_msg ) ); + + $cmd = 'php -ddisable_functions=proc_close php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'WP_CLI\\Utils\\run_mysql_command( null, array() );' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, $err_msg ) ); + } + + public function testLaunchEditorForInputProcDisabled() { + $err_msg = 'Error: Cannot do \'launch_editor_for_input\': The PHP functions `proc_open()` and/or `proc_close()` are disabled'; + + $cmd = 'php -ddisable_functions=proc_open php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'WP_CLI\\Utils\\launch_editor_for_input( null, null );' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, $err_msg ) ); + + $cmd = 'php -ddisable_functions=proc_close php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'WP_CLI\\Utils\\launch_editor_for_input( null, null );' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, $err_msg ) ); + } + + /** + * @dataProvider dataPastTenseVerb + */ + public function testPastTenseVerb( $verb, $expected ) { + $this->assertSame( $expected, Utils\past_tense_verb( $verb ) ); + } + + public function dataPastTenseVerb() { + return array( + // Known to be used by commands. + array( 'activate', 'activated' ), + array( 'deactivate', 'deactivated' ), + array( 'delete', 'deleted' ), + array( 'import', 'imported' ), + array( 'install', 'installed' ), + array( 'network activate', 'network activated' ), + array( 'network deactivate', 'network deactivated' ), + array( 'regenerate', 'regenerated' ), + array( 'reset', 'reset' ), + array( 'spam', 'spammed' ), + array( 'toggle', 'toggled' ), + array( 'uninstall', 'uninstalled' ), + array( 'update', 'updated' ), + // Some others. + array( 'call', 'called' ), + array( 'check', 'checked' ), + array( 'crop', 'cropped' ), + array( 'fix', 'fixed' ), // One vowel + final "x" excluded. + array( 'ah', 'ahed' ), // One vowel + final "h" excluded. + array( 'show', 'showed' ), // One vowel + final "w" excluded. + array( 'ski', 'skied' ), + array( 'slay', 'slayed' ), // One vowel + final "y" excluded (nearly all irregular anyway). + array( 'submit', 'submited' ), // BUG: multi-voweled verbs that double not catered for - should be "submitted". + array( 'try', 'tried' ), + ); + } + + /** + * @dataProvider dataExpandGlobs + */ + public function testExpandGlobs( $path, $expected ) { + $expand_globs_no_glob_brace = getenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ); + + $dir = __DIR__ . '/data/expand_globs/'; + $expected = array_map( function ( $v ) use ( $dir ) { return $dir . $v; }, $expected ); + + putenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=0' ); + $out = Utils\expand_globs( $dir . $path ); + $this->assertSame( $expected, $out ); + + putenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=1' ); + $out = Utils\expand_globs( $dir . $path ); + $this->assertSame( $expected, $out ); + + putenv( false === $expand_globs_no_glob_brace ? 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' : "WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE=$expand_globs_no_glob_brace" ); + } + + public function dataExpandGlobs() { + // Files in "data/expand_globs": foo.ab1, foo.ab2, foo.efg1, foo.efg2, bar.ab1, bar.ab2, baz.ab1, baz.ac1, baz.efg2. + return array( + array( 'foo.ab1', array( 'foo.ab1' ) ), + array( '{foo,bar}.ab1', array( 'foo.ab1', 'bar.ab1' ) ), + array( '{foo,baz}.a{b,c}1', array( 'foo.ab1', 'baz.ab1' , 'baz.ac1' ) ), + array( '{foo,baz}.{ab,ac}1', array( 'foo.ab1', 'baz.ab1' , 'baz.ac1' ) ), + array( '{foo,bar}.{ab1,efg1}', array( 'foo.ab1', 'foo.efg1', 'bar.ab1' ) ), + array( '{foo,bar,baz}.{ab,ac,efg}1', array( 'foo.ab1', 'foo.efg1', 'bar.ab1', 'baz.ab1', 'baz.ac1' ) ), + array( '{foo,ba{r,z}}.ab1', array( 'foo.ab1', 'bar.ab1', 'baz.ab1' ) ), + array( '{foo,ba{r,z}}.{ab1,efg1}', array( 'foo.ab1', 'foo.efg1', 'bar.ab1', 'baz.ab1') ), + array( '{foo,bar}.{ab{1,2},efg1}', array( 'foo.ab1', 'foo.ab2', 'foo.efg1', 'bar.ab1', 'bar.ab2' ) ), + array( '{foo,ba{r,z}}.{a{b,c}{1,2},efg{1,2}}', array( 'foo.ab1', 'foo.ab2', 'foo.efg1', 'foo.efg2', 'bar.ab1', 'bar.ab2', 'baz.ab1', 'baz.ac1', 'baz.efg2' ) ), + + array( 'no_such_file', array( 'no_such_file' ) ), // Documenting this behaviour here, which is odd (though advertized) - more natural to return an empty array. + ); + } + + /** + * @dataProvider dataReportBatchOperationResults + */ + public function testReportBatchOperationResults( $stdout, $stderr, $noun, $verb, $total, $successes, $failures, $skips ) { + // Save WP_CLI state. + $class_wp_cli_logger = new \ReflectionProperty( 'WP_CLI', 'logger' ); + $class_wp_cli_logger->setAccessible( true ); + $class_wp_cli_capture_exit = new \ReflectionProperty( 'WP_CLI', 'capture_exit' ); + $class_wp_cli_capture_exit->setAccessible( true ); + + $prev_logger = $class_wp_cli_logger->getValue(); + $prev_capture_exit = $class_wp_cli_capture_exit->getValue(); + + // Enable exit exception. + $class_wp_cli_capture_exit->setValue( true ); + + $logger = new \WP_CLI\Loggers\Execution; + WP_CLI::set_logger( $logger ); + + $exception = null; + + try { + Utils\report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips ); + } catch ( \WP_CLI\ExitException $ex ) { + $exception = $ex; + } + $this->assertSame( $stdout, $logger->stdout ); + $this->assertSame( $stderr, $logger->stderr ); + + // Restore. + $class_wp_cli_logger->setValue( $prev_logger ); + $class_wp_cli_capture_exit->setValue( $prev_capture_exit ); + } + + public function dataReportBatchOperationResults() { + return array( + array( "Success: Noun already verbed.\n", '', 'noun', 'verb', 1, 0, 0, null ), + array( "Success: Verbed 1 of 1 nouns.\n", '', 'noun', 'verb', 1, 1, 0, null ), + array( "Success: Verbed 1 of 2 nouns.\n", '', 'noun', 'verb', 2, 1, 0, null ), + array( "Success: Verbed 2 of 2 nouns.\n", '', 'noun', 'verb', 2, 2, 0, 0 ), + array( "Success: Verbed 1 of 2 nouns (1 skipped).\n", '', 'noun', 'verb', 2, 1, 0, 1 ), + array( "Success: Verbed 2 of 4 nouns (2 skipped).\n", '', 'noun', 'verb', 4, 2, 0, 2 ), + array( '', "Error: No nouns verbed.\n", 'noun', 'verb', 1, 0, 1, null ), + array( '', "Error: No nouns verbed.\n", 'noun', 'verb', 2, 0, 1, null ), + array( '', "Error: No nouns verbed (2 failed).\n", 'noun', 'verb', 3, 0, 2, 0 ), + array( '', "Error: No nouns verbed (2 failed, 1 skipped).\n", 'noun', 'verb', 3, 0, 2, 1 ), + array( '', "Error: Only verbed 1 of 2 nouns.\n", 'noun', 'verb', 2, 1, 1, null ), + array( '', "Error: Only verbed 1 of 3 nouns (2 failed).\n", 'noun', 'verb', 3, 1, 2, 0 ), + array( '', "Error: Only verbed 1 of 6 nouns (3 failed, 2 skipped).\n", 'noun', 'verb', 6, 1, 3, 2 ), + ); + } + + public function testGetPHPBinary() { + $env_php_used = getenv( 'WP_CLI_PHP_USED' ); + $env_php = getenv( 'WP_CLI_PHP' ); + + putenv( 'WP_CLI_PHP_USED' ); + putenv( 'WP_CLI_PHP' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertTrue( is_executable( $get_php_binary ) ); + + putenv( 'WP_CLI_PHP_USED=/my-php-5.3' ); + putenv( 'WP_CLI_PHP' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertSame( $get_php_binary, '/my-php-5.3' ); + + putenv( 'WP_CLI_PHP=/my-php-7.3' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertSame( $get_php_binary, '/my-php-5.3' ); // WP_CLI_PHP_USED wins. + + putenv( 'WP_CLI_PHP_USED' ); + $get_php_binary = Utils\get_php_binary(); + $this->assertSame( $get_php_binary, '/my-php-7.3' ); + + putenv( false === $env_php_used ? 'WP_CLI_PHP_USED' : "WP_CLI_PHP_USED=$env_php_used" ); + putenv( false === $env_php ? 'WP_CLI_PHP' : "WP_CLI_PHP=$env_php" ); + } + + /** + * @dataProvider dataProcOpenCompatWinEnv + */ + public function testProcOpenCompatWinEnv( $cmd, $env, $expected_cmd, $expected_env ) { + $env_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ); + + putenv( 'WP_CLI_TEST_IS_WINDOWS=1' ); + + $cmd = Utils\_proc_open_compat_win_env( $cmd, $env ); + $this->assertSame( $expected_cmd, $cmd ); + $this->assertSame( $expected_env, $env ); + + putenv( false === $env_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$env_is_windows" ); + } + + function dataProcOpenCompatWinEnv() { + return array( + array( 'echo', array(), 'echo', array() ), + array( 'ENV=blah echo', array(), 'echo', array( 'ENV' => 'blah' ) ), + array( 'ENV="blah blah" echo', array(), 'echo', array( 'ENV' => 'blah blah' ) ), + array( 'ENV_1="blah1 blah1" ENV_2="blah2" ENV_3=blah3 echo', array(), 'echo', array( 'ENV_1' => 'blah1 blah1', 'ENV_2' => 'blah2', 'ENV_3' => 'blah3' ) ), + array( 'ENV= echo', array(), 'echo', array( 'ENV' => '' ) ), + array( 'ENV=0 echo', array(), 'echo', array( 'ENV' => '0' ) ), + + // With `$env` set. + array( 'echo', array( 'ENV' => 'in' ), 'echo', array( 'ENV' => 'in' ) ), + array( 'ENV=blah echo', array( 'ENV_1' => 'in1', 'ENV_2' => 'in2' ), 'echo', array( 'ENV_1' => 'in1', 'ENV_2' => 'in2', 'ENV' => 'blah' ) ), + array( 'ENV="blah blah" echo', array( 'ENV' => 'in' ), 'echo', array( 'ENV' => 'blah blah' ) ), + + // Special cases. + array( '1=1 echo', array(), '1=1 echo', array() ), // Must begin with alphabetic or underscore. + array( '_eNv=1 echo', array(), 'echo', array( '_eNv' => '1' ) ), // Mixed-case and beginning with underscore allowed. + array( 'ENV=\'blah blah\' echo', array(), 'blah\' echo', array( 'ENV' => '\'blah' ) ), // Unix escaping not supported, ie treated literally. + ); + } + + /** + * Copied from core "tests/phpunit/tests/db.php" (adapted to not use `$wpdb`). + */ + function test_esc_like() { + $inputs = array( + 'howdy%', //Single Percent + 'howdy_', //Single Underscore + 'howdy\\', //Single slash + 'howdy\\howdy%howdy_', //The works + 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?', //Plain text + ); + $expected = array( + 'howdy\\%', + 'howdy\\_', + 'howdy\\\\', + 'howdy\\\\howdy\\%howdy\\_', + 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?', + ); + + foreach ( $inputs as $key => $input ) { + $this->assertEquals( $expected[ $key ], Utils\esc_like( $input ) ); + } + } + + /** @dataProvider dataIsJson */ + public function testIsJson( $argument, $ignore_scalars, $expected ) { + $this->assertEquals( $expected, Utils\is_json( $argument, $ignore_scalars ) ); + } + + public function dataIsJson() { + return array( + array( '42', true, false ), + array( '42', false, true ), + array( '"test"', true, false ), + array( '"test"', false, true ), + array( '{"key1":"value1","key2":"value2"}', true, true ), + array( '{"key1":"value1","key2":"value2"}', false, true ), + array( '["value1","value2"]', true, true ), + array( '["value1","value2"]', false, true ), + ); + } + + /** @dataProvider dataParseShellArray */ + public function testParseShellArray( $assoc_args, $array_arguments, $expected ) { + $this->assertEquals( $expected, Utils\parse_shell_arrays( $assoc_args, $array_arguments ) ); + } + + public function dataParseShellArray() { + return array( + array( array( 'alpha' => '{"key":"value"}' ), array(), array( 'alpha' => '{"key":"value"}' ) ), + array( array( 'alpha' => '{"key":"value"}' ), array( 'alpha' ), array( 'alpha' => array( 'key' => 'value' ) ) ), + array( array( 'alpha' => '{"key":"value"}' ), array( 'beta' ), array( 'alpha' => '{"key":"value"}' ) ), + ); + } +} diff --git a/tests/test-wp-cli.php b/tests/test-wp-cli.php new file mode 100644 index 000000000..209fef8c7 --- /dev/null +++ b/tests/test-wp-cli.php @@ -0,0 +1,24 @@ +&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, $err_msg ) ); + + $cmd = 'php -ddisable_functions=proc_close php/boot-fs.php --skip-wordpress eval ' . escapeshellarg( 'WP_CLI::launch( null );' ) . ' 2>&1'; + $output = array(); + exec( $cmd, $output ); + $output = trim( implode( "\n", $output ) ); + $this->assertTrue( false !== strpos( $output, $err_msg ) ); + } + + public function testGetPHPBinary() { + $this->assertSame( WP_CLI\Utils\get_php_binary(), WP_CLI::get_php_binary() ); + } +} diff --git a/tests/WpVersionCompareTest.php b/tests/test-wp-version-compare.php similarity index 97% rename from tests/WpVersionCompareTest.php rename to tests/test-wp-version-compare.php index 0e9817b50..73c320930 100644 --- a/tests/WpVersionCompareTest.php +++ b/tests/test-wp-version-compare.php @@ -1,14 +1,13 @@ assertTrue( Utils\wp_version_compare( '4.8', '>=' ) ); $this->assertFalse( Utils\wp_version_compare( '4.8', '<' ) ); @@ -75,4 +74,5 @@ public function testBasic(): void { $this->assertTrue( Utils\wp_version_compare( '4.9', '>=' ) ); $this->assertFalse( Utils\wp_version_compare( '4.9', '<' ) ); } + } diff --git a/utils/auto-composer-update.sh b/utils/auto-composer-update.sh index 3b61dcbb0..ce3c47c9b 100755 --- a/utils/auto-composer-update.sh +++ b/utils/auto-composer-update.sh @@ -43,7 +43,7 @@ MESSAGE="Update Composer dependencies ($DATE) \`\`\` $UPDATE \`\`\`" -git commit -n -m "$MESSAGE" +git commit -m "$MESSAGE" # Push and pull request git push origin $BRANCH diff --git a/utils/contrib-list.php b/utils/contrib-list.php new file mode 100644 index 000000000..5ab696c54 --- /dev/null +++ b/utils/contrib-list.php @@ -0,0 +1,205 @@ +] + * : Render output in a specific format. + * --- + * default: markdown + * options: + * - markdown + * - html + * --- + * + * @when before_wp_load + */ + public function __invoke( $_, $assoc_args ) { + + $contributors = array(); + $contributor_count = 0; + $pull_request_count = 0; + + // Get the contributors to the current open large project milestones + foreach( array( 'wp-cli/wp-cli', 'wp-cli/handbook', 'wp-cli/wp-cli.github.com' ) as $repo ) { + $milestones = self::get_project_milestones( $repo ); + // Cheap way to get the latest milestone + $milestone = array_shift( $milestones ); + WP_CLI::log( 'Current open ' . $repo . ' milestone: ' . $milestone->title ); + $pull_requests = self::get_project_milestone_pull_requests( $repo, $milestone->number ); + $repo_contributors = self::parse_contributors_from_pull_requests( $pull_requests ); + WP_CLI::log( ' - Contributors: ' . count( $repo_contributors ) ); + WP_CLI::log( ' - Pull requests: ' . count( $pull_requests ) ); + $pull_request_count += count( $pull_requests ); + $contributors = array_merge( $contributors, $repo_contributors ); + } + + // Identify all command dependencies and their contributors + $milestones = self::get_project_milestones( 'wp-cli/wp-cli', array( 'state' => 'closed' ) ); + // Cheap way to get the latest closed milestone + $milestone = array_shift( $milestones ); + $composer_lock_url = sprintf( 'https://raw.githubusercontent.com/wp-cli/wp-cli/v%s/composer.lock', $milestone->title ); + WP_CLI::log( 'Fetching ' . $composer_lock_url ); + $response = Utils\http_request( 'GET', $composer_lock_url ); + if ( 200 !== $response->status_code ) { + WP_CLI::error( sprintf( 'Could not fetch composer.json (HTTP code %d)', $response->status_code ) ); + } + $composer_json = json_decode( $response->body, true ); + foreach( $composer_json['packages'] as $package ) { + $package_name = $package['name']; + $version_constraint = str_replace( 'v', '', $package['version'] ); + if ( ! preg_match( '#^wp-cli/.+-command$#', $package_name ) ) { + continue; + } + // Closed milestones denote a tagged release + $milestones = self::get_project_milestones( $package_name, array( 'state' => 'closed' ) ); + $milestone_ids = array(); + $milestone_titles = array(); + foreach( $milestones as $milestone ) { + if ( ! version_compare( $milestone->title, $version_constraint, '>' ) ) { + continue; + } + $milestone_ids[] = $milestone->number; + $milestone_titles[] = $milestone->title; + } + // No shipped releases for this milestone. + if ( empty( $milestone_ids ) ) { + continue; + } + WP_CLI::log( 'Closed ' . $package_name . ' milestone(s): ' . implode( ', ', $milestone_titles ) ); + foreach( $milestone_ids as $milestone_id ) { + $pull_requests = self::get_project_milestone_pull_requests( $package_name, $milestone_id ); + $repo_contributors = self::parse_contributors_from_pull_requests( $pull_requests ); + WP_CLI::log( ' - Contributors: ' . count( $repo_contributors ) ); + WP_CLI::log( ' - Pull requests: ' . count( $pull_requests ) ); + $pull_request_count += count( $pull_requests ); + $contributors = array_merge( $contributors, $repo_contributors ); + } + } + + WP_CLI::log( 'Total contributors: ' . count( $contributors ) ); + WP_CLI::log( 'Total pull requests: ' . $pull_request_count ); + + // Sort and render the contributor list + asort( $contributors, SORT_NATURAL | SORT_FLAG_CASE ); + if ( in_array( $assoc_args['format'], array( 'markdown', 'html' ) ) ) { + $contrib_list = ''; + foreach( $contributors as $url => $login ) { + if ( 'markdown' === $assoc_args['format'] ) { + $contrib_list .= '[' . $login . '](' . $url . '), '; + } elseif ( 'html' === $assoc_args['format'] ) { + $contrib_list .= '' . $login . ', '; + } + } + $contrib_list = rtrim( $contrib_list, ', ' ); + WP_CLI::log( $contrib_list ); + } + } + + /** + * Get the milestones for a given project + * + * @param string $project + * @return array + */ + private static function get_project_milestones( $project, $args = array() ) { + $request_url = sprintf( 'https://api.github.com/repos/%s/milestones', $project ); + list( $body, $headers ) = self::make_github_api_request( $request_url, $args ); + return $body; + } + + /** + * Get the pull requests assigned to a milestone of a given project + * + * @param string $project + * @param integer $milestone_id + * @return array + */ + private static function get_project_milestone_pull_requests( $project, $milestone_id ) { + $request_url = sprintf( 'https://api.github.com/repos/%s/issues', $project ); + $args = array( + 'milestone' => $milestone_id, + 'state' => 'all', + ); + $pull_requests = array(); + do { + list( $body, $headers ) = self::make_github_api_request( $request_url, $args ); + foreach( $body as $issue ) { + if ( ! empty( $issue->pull_request ) ) { + $pull_requests[] = $issue; + } + } + $args = array(); + $request_url = false; + // Set $request_url to 'rel="next" if present' + if ( ! empty( $headers['Link'] ) ) { + $bits = explode( ',', $headers['Link'] ); + foreach( $bits as $bit ) { + if ( false !== stripos( $bit, 'rel="next"' ) ) { + $hrefandrel = explode( '; ', $bit ); + $request_url = trim( $hrefandrel[0], '<>' ); + break; + } + } + } + } while( $request_url ); + return $pull_requests; + } + + /** + * Parse the contributors from pull request objects + * + * @param array $pull_requests + * @return array + */ + private static function parse_contributors_from_pull_requests( $pull_requests ) { + $contributors = array(); + foreach( $pull_requests as $pull_request ) { + if ( ! empty( $pull_request->user ) ) { + $contributors[ $pull_request->user->html_url ] = $pull_request->user->login; + } + } + return $contributors; + } + + /** + * Make a request to the GitHub API + * + * @param string $url + * @param string $args + * @return array + */ + private static function make_github_api_request( $url, $args = array() ) { + $headers = array( + 'Accept' => 'application/vnd.github.v3+json', + 'User-Agent' => 'WP-CLI', + ); + if ( $token = getenv( 'GITHUB_TOKEN' ) ) { + $headers['Authorization'] = 'token ' . $token; + } + $response = Utils\http_request( 'GET', $url, $args, $headers ); + if ( 200 !== $response->status_code ) { + WP_CLI::error( sprintf( 'GitHub API returned: %s (HTTP code %d)', $response->body, $response->status_code ) ); + } + return array( json_decode( $response->body ), $response->headers ); + } + +} + +WP_CLI::add_command( 'contrib-list', 'Contrib_List_Command' ); diff --git a/utils/get-package-require-from-composer.php b/utils/get-package-require-from-composer.php index 6639ddd7f..33e30ccfd 100644 --- a/utils/get-package-require-from-composer.php +++ b/utils/get-package-require-from-composer.php @@ -3,7 +3,7 @@ $file = $argv[1]; if ( ! file_exists( $file ) ) { echo 'File does not exist.'; - exit( 1 ); + exit(1); } $contents = file_get_contents( $file ); @@ -11,13 +11,13 @@ if ( empty( $composer ) || ! is_object( $composer ) ) { echo 'Invalid composer.json for package.'; - exit( 1 ); + exit(1); } if ( empty( $composer->autoload->files ) ) { echo 'composer.json must specify valid "autoload" => "files"'; - exit( 1 ); + exit(1); } echo implode( PHP_EOL, $composer->autoload->files ); -exit( 0 ); +exit(0); \ No newline at end of file diff --git a/utils/install-requests.sh b/utils/install-requests.sh deleted file mode 100755 index 6c2c3fd93..000000000 --- a/utils/install-requests.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -REQUESTS_TAG="v2.0.18" - -DOWNLOAD_LINK="https://github.com/WordPress/Requests/archive/refs/tags/${REQUESTS_TAG}.tar.gz" - -SCRIPT_DIR=$(dirname "$0") - -BUNDLE_DIR="${SCRIPT_DIR}/../bundle" - -# First check if Requests is already installed. -if [ -d "${BUNDLE_DIR}/rmccue/requests" ]; then - - # Check if the version is correct. - if [ -f "${BUNDLE_DIR}/rmccue/requests/src/Requests/Requests.php" ]; then - VERSION=$(grep -oP "const VERSION = '\K[0-9\.]*" ${BUNDLE_DIR}/rmccue/requests/src/Requests.php) - if [ "$VERSION" == "$REQUESTS_TAG" ]; then - exit 0 - fi - fi -fi - -# Remove old version. -rm -rf "${BUNDLE_DIR}/rmccue" - -# Download and extract Requests. -mkdir -p "${BUNDLE_DIR}/rmccue/requests" -curl -L "${DOWNLOAD_LINK}" | tar xz -C "${BUNDLE_DIR}/rmccue/requests" --strip-components=1 diff --git a/utils/make-phar-spec.php b/utils/make-phar-spec.php new file mode 100644 index 000000000..2a2ad03f9 --- /dev/null +++ b/utils/make-phar-spec.php @@ -0,0 +1,37 @@ + array( + 'runtime' => '=', + 'file' => '', + 'desc' => 'Path to output file', + ), + + 'version' => array( + 'runtime' => '=', + 'file' => '', + 'desc' => 'New package version', + ), + + 'store-version' => array( + 'runtime' => '', + 'file' => '', + 'default' => false, + 'desc' => 'If true the contents of ./VERSION will be set to the value passed to --version', + ), + + 'quiet' => array( + 'runtime' => '', + 'file' => '', + 'default' => false, + 'desc' => 'Suppress informational messages', + ), + + 'build' => array( + 'runtime' => '=', + 'file' => '', + 'default' => '', + 'desc' => 'Create a minimum test build "cli", that only supports cli commands', + ), +); + diff --git a/utils/make-phar.php b/utils/make-phar.php new file mode 100644 index 000000000..68f29fdee --- /dev/null +++ b/utils/make-phar.php @@ -0,0 +1,257 @@ +parse_args( array_slice( $GLOBALS['argv'], 1 ) ); + +if ( ! isset( $args[0] ) || empty( $args[0] ) ) { + echo "usage: php -dphar.readonly=0 $argv[0] [--quiet] [--version=same|patch|minor|major|x.y.z] [--store-version]\n"; + exit(1); +} + +define( 'DEST_PATH', $args[0] ); + +define( 'BE_QUIET', isset( $runtime_config['quiet'] ) && $runtime_config['quiet'] ); + +define( 'BUILD', isset( $runtime_config['build'] ) ? $runtime_config['build'] : '' ); + +$current_version = trim( file_get_contents( WP_CLI_ROOT . '/VERSION' ) ); + +if ( isset( $runtime_config['version'] ) ) { + $new_version = $runtime_config['version']; + $new_version = Utils\increment_version( $current_version, $new_version ); + + if ( isset( $runtime_config['store-version'] ) && $runtime_config['store-version'] ) { + file_put_contents( WP_CLI_ROOT . '/VERSION', $new_version ); + } + + $current_version = $new_version; +} + +function add_file( $phar, $path ) { + $key = str_replace( WP_CLI_BASE_PATH, '', $path ); + + if ( !BE_QUIET ) + echo "$key - $path\n"; + + $basename = basename( $path ); + if ( 0 === strpos( $basename, 'autoload_' ) && preg_match( '/(?:classmap|files|namespaces|psr4|static)\.php$/', $basename ) ) { + // Strip autoload maps of unused stuff. + static $strip_res = null; + if ( null === $strip_res ) { + if ( 'cli' === BUILD ) { + $strips = array( + '\/(?:behat|composer|gherkin)\/src', + '\/phpunit\/', + '\/nb\/oxymel', + '-command\/src\/', + '\/wp-cli\/[^\n]+-command\/', + '\/symfony\/(?!finder|polyfill-mbstring)[^\/]+\/', + '\/(?:dealerdirect|squizlabs|wimg)\/', + ); + } else { + $strips = array( + '\/(?:behat|gherkin)\/src\/', + '\/phpunit\/', + '\/symfony\/(?!console|filesystem|finder|polyfill-mbstring|process)[^\/]+\/', + '\/composer\/spdx-licenses\/', + '\/Composer\/(?:Command\/|Compiler\.php|Console\/|Downloader\/Pear|Installer\/Pear|Question\/|Repository\/Pear|SelfUpdate\/)', + '\/(?:dealerdirect|squizlabs|wimg)\/', + ); + } + $strip_res = array_map( function ( $v ) { + return '/^[^,\n]+?' . $v . '[^,\n]+?, *\n/m'; + }, $strips ); + } + $phar[ $key ] = preg_replace( $strip_res, '', file_get_contents( $path ) ); + } else { + $phar[ $key ] = file_get_contents( $path ); + } +} + +function set_file_contents( $phar, $path, $content ) { + $key = str_replace( WP_CLI_BASE_PATH, '', $path ); + + if ( !BE_QUIET ) + echo "$key - $path\n"; + + $phar[ $key ] = $content; +} + +if ( file_exists( DEST_PATH ) ) { + unlink( DEST_PATH ); +} +$phar = new Phar( DEST_PATH, 0, 'wp-cli.phar' ); + +$phar->startBuffering(); + +// PHP files +$finder = new Finder(); +$finder + ->files() + ->ignoreVCS(true) + ->name('*.php') + ->in(WP_CLI_ROOT . '/php') + ->in(WP_CLI_VENDOR_DIR . '/mustache') + ->in(WP_CLI_VENDOR_DIR . '/rmccue/requests') + ->in(WP_CLI_VENDOR_DIR . '/composer') + ->in(WP_CLI_VENDOR_DIR . '/ramsey/array_column') + ->in(WP_CLI_VENDOR_DIR . '/symfony/finder') + ->in(WP_CLI_VENDOR_DIR . '/symfony/polyfill-mbstring') + ->notName('behat-tags.php') + ->notPath('#(?:[^/]+-command|php-cli-tools)/vendor/#') // For running locally, in case have composer installed or symlinked them. + ->exclude('examples') + ->exclude('features') + ->exclude('test') + ->exclude('tests') + ->exclude('Test') + ->exclude('Tests') + ; +if ( 'cli' === BUILD ) { + $finder + ->in(WP_CLI_VENDOR_DIR . '/wp-cli/mustangostang-spyc') + ->in(WP_CLI_VENDOR_DIR . '/wp-cli/php-cli-tools') + ->in(WP_CLI_VENDOR_DIR . '/seld/cli-prompt') + ->exclude('composer/ca-bundle') + ->exclude('composer/semver') + ->exclude('composer/src') + ->exclude('composer/spdx-licenses') + ; +} else { + $finder + ->in(WP_CLI_VENDOR_DIR . '/wp-cli') + ->in(WP_CLI_ROOT . '/features/bootstrap') // These are required for scaffold-package-command. + ->in(WP_CLI_ROOT . '/features/steps') + ->in(WP_CLI_ROOT . '/features/extra') + ->in(WP_CLI_VENDOR_DIR . '/nb/oxymel') + ->in(WP_CLI_VENDOR_DIR . '/psr') + ->in(WP_CLI_VENDOR_DIR . '/seld') + ->in(WP_CLI_VENDOR_DIR . '/symfony/console') + ->in(WP_CLI_VENDOR_DIR . '/symfony/filesystem') + ->in(WP_CLI_VENDOR_DIR . '/symfony/process') + ->in(WP_CLI_VENDOR_DIR . '/justinrainbow/json-schema') + ->exclude('nb/oxymel/OxymelTest.php') + ->exclude('composer/spdx-licenses') + ->exclude('composer/composer/src/Composer/Command') + ->exclude('composer/composer/src/Composer/Compiler.php') + ->exclude('composer/composer/src/Composer/Console') + ->exclude('composer/composer/src/Composer/Downloader/PearPackageExtractor.php') // Assuming Pear installation isn't supported by wp-cli. + ->exclude('composer/composer/src/Composer/Installer/PearBinaryInstaller.php') + ->exclude('composer/composer/src/Composer/Installer/PearInstaller.php') + ->exclude('composer/composer/src/Composer/Question') + ->exclude('composer/composer/src/Composer/Repository/Pear') + ->exclude('composer/composer/src/Composer/SelfUpdate') + ; +} + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +// other files +$finder = new Finder(); +$finder + ->files() + ->ignoreVCS(true) + ->ignoreDotFiles(false) + ->in( WP_CLI_ROOT . '/templates') + ; + +foreach ( $finder as $file ) { + add_file( $phar, $file ); +} + +if ( 'cli' !== BUILD ) { + // Include base project files, because the autoloader will load them + if ( WP_CLI_BASE_PATH !== WP_CLI_ROOT ) { + $finder = new Finder(); + $finder + ->files() + ->ignoreVCS(true) + ->name('*.php') + ->in(WP_CLI_BASE_PATH . '/src') + ->exclude('test') + ->exclude('tests') + ->exclude('Test') + ->exclude('Tests'); + foreach ( $finder as $file ) { + add_file( $phar, $file ); + } + // Any PHP files in the project root + foreach ( glob( WP_CLI_BASE_PATH . '/*.php' ) as $file ) { + add_file( $phar, $file ); + } + } + + $finder = new Finder(); + $finder + ->files() + ->ignoreVCS(true) + ->ignoreDotFiles(false) + ->in( WP_CLI_VENDOR_DIR . '/wp-cli/config-command/templates') + ; + foreach ( $finder as $file ) { + add_file( $phar, $file ); + } + + $finder = new Finder(); + $finder + ->files() + ->ignoreVCS(true) + ->ignoreDotFiles(false) + ->in( WP_CLI_VENDOR_DIR . '/wp-cli/scaffold-command/templates') + ; + foreach ( $finder as $file ) { + add_file( $phar, $file ); + } +} + +add_file( $phar, WP_CLI_VENDOR_DIR . '/autoload.php' ); +add_file( $phar, WP_CLI_VENDOR_DIR . '/autoload_commands.php' ); +add_file( $phar, WP_CLI_VENDOR_DIR . '/autoload_framework.php' ); +if ( 'cli' !== BUILD ) { + add_file( $phar, WP_CLI_ROOT . '/ci/behat-tags.php' ); + add_file( $phar, WP_CLI_VENDOR_DIR . '/composer/composer/LICENSE' ); + add_file( $phar, WP_CLI_VENDOR_DIR . '/composer/composer/res/composer-schema.json' ); +} +add_file( $phar, WP_CLI_VENDOR_DIR . '/rmccue/requests/library/Requests/Transport/cacert.pem' ); + +set_file_contents( $phar, WP_CLI_ROOT . '/VERSION', $current_version ); + +$phar_boot = str_replace( WP_CLI_BASE_PATH, '', WP_CLI_ROOT . '/php/boot-phar.php' ); +$phar->setStub( << +EOB +); + +$phar->stopBuffering(); + +chmod( DEST_PATH, 0755 ); // Make executable. + +if ( ! BE_QUIET ) { + echo "Generated " . DEST_PATH . "\n"; +} diff --git a/utils/phpstan/scan-files.php b/utils/phpstan/scan-files.php deleted file mode 100644 index 1e97c4483..000000000 --- a/utils/phpstan/scan-files.php +++ /dev/null @@ -1,145 +0,0 @@ - $idents A single identifier or an array of identifiers. - * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings. - * - * @phpstan-return ($idents is string ? string : array) - */ - function esc_sql( $idents ) { - } -} - -namespace LCache { - class Integrated { - } -} - -namespace WpOrg\Requests { - class Exception extends \Exception { - /** - * Like {@see \Exception::getCode()}, but a string code. - * - * @return string - */ - public function getType() { - } - - /** - * Gives any relevant data - * - * @return mixed - */ - public function getData() { - } - } - - class Response { - /** - * Response body - * - * @var string - */ - public $body = ''; - - /** - * Raw HTTP data from the transport - * - * @var string - */ - public $raw = ''; - - /** - * Headers, as an associative array - * - * @var \WpOrg\Requests\Response\Headers Array-like object representing headers - */ - public $headers = []; - - /** - * Status code, false if non-blocking - * - * @var integer|boolean - */ - public $status_code = false; - - /** - * Protocol version, false if non-blocking - * - * @var float|boolean - */ - public $protocol_version = false; - - /** - * Whether the request succeeded or not - * - * @var boolean - */ - public $success = false; - - /** - * Number of redirects the request used - * - * @var integer - */ - public $redirects = 0; - - /** - * URL requested - * - * @var string - */ - public $url = ''; - - /** - * Previous requests (from redirects) - * - * @var array Array of \WpOrg\Requests\Response objects - */ - public $history = []; - - /** - * Cookies from the request - * - * @var array Array-like object representing a cookie jar - */ - public $cookies = []; - } -} diff --git a/utils/test-phar-download b/utils/test-phar-download new file mode 100755 index 000000000..f4c9ad7f4 --- /dev/null +++ b/utils/test-phar-download @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +actual_checksum=$(curl http://wp-cli.org/packages/phar/wp-cli.phar | md5sum | cut -d ' ' -f 1) + +echo "expected:" $(curl -s http://wp-cli.org/packages/phar/wp-cli.phar.md5) +echo "actual: " $actual_checksum + +actual_checksum=$(curl http://wp-cli.org/packages/phar/wp-cli.phar | sha512sum | cut -d ' ' -f 1) + +echo "expected SHA-512:" $(curl -s http://wp-cli.org/packages/phar/wp-cli.phar.sha512) +echo "actual SHA-512: " $actual_checksum diff --git a/utils/update-phar b/utils/update-phar new file mode 100755 index 000000000..e2b16973e --- /dev/null +++ b/utils/update-phar @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -ex + +version=${1-"same"} + +current_rev=$(git rev-parse HEAD) +current_rev=${current_rev:0:10} + +packages_repo=../wp-cli-packages + +fname="phar/wp-cli.phar" + +# generate archive +php -dphar.readonly=0 ./utils/make-phar.php $packages_repo/$fname --quiet --version=$version --store-version + +cd $packages_repo + +# smoke test +php $fname --version + +# check which wp-cli commit the previous Phar archive was based on +# can't use the md5 hash, since it will be different each time the +# archive is generated +new_commit_subj="update wp-cli.phar to wp-cli/wp-cli@$current_rev" + +current_commit_subj=$(git show -s --pretty=format:%s HEAD) + +if [ "$new_commit_subj" = "$current_commit_subj" ]; then + echo "already at latest revision" + exit 1 +fi + +# generate md5 checksum +if [ command -v md5sum > /dev/null ] +then + md5hash=$(md5sum $fname) +else + md5hash=$(md5 -r $fname) +fi + +echo $md5hash | cut -d ' ' -f 1 > $fname.md5 + +sha512sum $fname | cut -d ' ' -f 1 > $fname.sha512 + +git add $fname $fname.md5 $fname.sha512 + +git commit -m "$new_commit_subj" + +git push diff --git a/utils/wp-cli-rpm.spec b/utils/wp-cli-rpm.spec new file mode 100644 index 000000000..1d5ef032b --- /dev/null +++ b/utils/wp-cli-rpm.spec @@ -0,0 +1,50 @@ +Name: wp-cli +Version: 0.0.0 +Release: 2%{?dist} +Summary: The command line interface for WordPress +License: MIT +URL: http://wp-cli.org/ +Source0: wp-cli.phar +Source1: wp.1 +BuildArch: noarch + +%post +echo "PHP 5.3.29 or above must be installed." + +%description +WP-CLI is the command-line interface for WordPress. +You can update plugins, configure multisite installations +and much more, without using a web browser. + +%prep +chmod +x %{SOURCE0} +{ + echo '.TH "WP" "1"' + php %{SOURCE0} --help +} \ + | sed -e 's/^\([A-Z ]\+\)$/.SH "\1"/' \ + | sed -e 's/^ wp$/wp \\- The command line interface for WordPress/' \ + > %{SOURCE1} + +%build + +%install +mkdir -p %{buildroot}%{_bindir} +install -p -m 0755 %{SOURCE0} %{buildroot}%{_bindir}/wp +mkdir -p %{buildroot}%{_mandir}/man1 +install -p -m 0644 %{SOURCE1} %{buildroot}%{_mandir}/man1/ + +%files +%attr(0755, root, root) %{_bindir}/wp +%attr(0644, root, root) %{_mandir}/man1/wp.1* + +%changelog +* Tue Dec 12 2017 Murtaza Sarıaltun - 0.0.0-2 +- Remove php requirements. +- Update creating man page steps. +- Added output message. + +* Fri Jul 7 2017 Murtaza Sarıaltun - 0.0.0-1 +- First release of the spec file +- Check the spec file with `rpmlint -i -v wp-cli-rpm.spec` +- Build the package with `rpmbuild -bb wp-cli-rpm.spec` diff --git a/utils/wp-cli-updatedeb.sh b/utils/wp-cli-updatedeb.sh new file mode 100755 index 000000000..5432d9cea --- /dev/null +++ b/utils/wp-cli-updatedeb.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# +# Package wp-cli to be installed in Debian-compatible systems. +# Only the phar file is included. +# +# VERSION :0.2.4 +# DATE :2017-05-31 +# AUTHOR :Viktor Szépe +# LICENSE :The MIT License (MIT) +# URL :https://github.com/wp-cli/wp-cli/tree/master/utils +# BASH-VERSION :4.2+ + +# packages source path +DIR="php-wpcli" +# phar URL +PHAR="https://github.com/wp-cli/builds/raw/gh-pages/phar/wp-cli.phar" + +die() { + local RET="$1" + shift + + echo -e "$@" >&2 + exit "$RET" +} + +dump_control() { + cat > DEBIAN/control < +Section: php +Priority: optional +Depends: php5-cli (>= 5.3.29) | php-cli | php7-cli, php5-mysql | php5-mysqlnd | php7.0-mysql | php7.1-mysql, mysql-client | mariadb-client +Homepage: http://wp-cli.org/ +Description: wp-cli is a set of command-line tools for managing + WordPress installations. You can update plugins, set up multisite + installations and much more, without using a web browser. + +EOF +} + +set -e + +# deb's dir +if ! [ -d "$DIR" ]; then + mkdir "$DIR" || die 1 "Cannot create directory here: ${PWD}" +fi + +pushd "$DIR" + +# control file +if ! [ -r DEBIAN/control ]; then + mkdir DEBIAN + dump_control +fi + +# copyright +if ! [ -r usr/share/doc/php-wpcli/copyright ]; then + mkdir -p usr/share/doc/php-wpcli &> /dev/null + wget -nv -O usr/share/doc/php-wpcli/copyright https://github.com/wp-cli/wp-cli/raw/master/LICENSE +fi + +# changelog +if ! [ -r usr/share/doc/php-wpcli/changelog.gz ]; then + mkdir -p usr/share/doc/php-wpcli &> /dev/null + echo "Changelog can be found in the blog: https://make.wordpress.org/cli/" \ + | gzip -n -9 > usr/share/doc/php-wpcli/changelog.gz +fi + +# content dirs +[ -d usr/bin ] || mkdir -p usr/bin + +# download current version +wget -nv -O usr/bin/wp "$PHAR" || die 3 "Phar download failure" +chmod +x usr/bin/wp || die 4 "chmod failure" + +# get version +WPCLI_VER="$(usr/bin/wp cli version | cut -d " " -f 2)" +[ -z "$WPCLI_VER" ] && die 5 "Cannot get wp-cli version" +echo "Current version: ${WPCLI_VER}" + +# update version +sed -i -e "s/^Version: .*$/Version: ${WPCLI_VER}/" DEBIAN/control || die 6 "Version update failure" + +# minimal man page +if ! [ -r usr/share/man/man1/wp.1.gz ]; then + mkdir -p usr/share/man/man1 &> /dev/null + { + echo '.TH "WP" "1"' + usr/bin/wp --help + } \ + | sed 's/^\([A-Z ]\+\)$/.SH "\1"/' \ + | sed 's/^ wp$/wp \\- A command line interface for WordPress/' \ + | gzip -n -9 > usr/share/man/man1/wp.1.gz +fi + +# update MD5-s +find usr -type f -exec md5sum "{}" ";" > DEBIAN/md5sums || die 7 "md5sum creation failure" + +popd + +# build package in the current diretory +WPCLI_PKG="${PWD}/php-wpcli_${WPCLI_VER}_all.deb" +fakeroot dpkg-deb --build "$DIR" "$WPCLI_PKG" || die 8 "Packaging failed" + +# check package - not critical +lintian --display-info --display-experimental --pedantic --show-overrides php-wpcli_*_all.deb || true + +# optional steps +echo "sign it: dpkg-sig -k SIGNING-KEY -s builder \"${WPCLI_PKG}\"" +echo "include in your repo: pushd /var/www/REPO-DIR" +echo " reprepro includedeb jessie \"${WPCLI_PKG}\" && popd" diff --git a/utils/wp-cli-updaterpm.sh b/utils/wp-cli-updaterpm.sh new file mode 100755 index 000000000..5bf04a4ba --- /dev/null +++ b/utils/wp-cli-updaterpm.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# +# Package WP-CLI to be installed on RPM-based systems. +# +# VERSION :0.1.0 +# DATE :2017-07-12 +# AUTHOR :Viktor Szépe +# LICENSE :The MIT License (MIT) +# URL :https://github.com/wp-cli/wp-cli/tree/master/utils +# BASH-VERSION :4.2+ +# DEPENDS :apt-get install rpm rpmlint php-cli + +PHAR_URL="https://github.com/wp-cli/builds/raw/gh-pages/phar/wp-cli.phar" +# Source directory +SOURCE_DIR="rpm-src" + +die() { + local RET="$1" + shift + + echo -e "$@" >&2 + exit "$RET" +} + +set -e + +# Check dependencies +if ! hash php rpm; then + die 1 "Missing RPM build tools" +fi + +if ! [ -d "$SOURCE_DIR" ]; then + mkdir "$SOURCE_DIR" || die 2 "Cannot create directory here: ${PWD}" +fi + +pushd "$SOURCE_DIR" > /dev/null + +# Download the binary +wget -nv -O wp-cli.phar "$PHAR_URL" +chmod +x wp-cli.phar + +# Copy spec file +cp ../wp-cli-rpm.spec wp-cli.spec + +# Replace version placeholder +WPCLI_VER="$(php wp-cli.phar cli version | cut -d " " -f 2)" +if [ -z "$WPCLI_VER" ]; then + die 3 "Cannot get WP_CLI version" +fi +echo "Current version: ${WPCLI_VER}" +sed -i -e "s/^Version: .*\$/Version: ${WPCLI_VER}/" wp-cli.spec || die 4 "Version update failed" +sed -i -e "s/^\(\* .*\) 0\.0\.0-1\$/\1 ${WPCLI_VER}-1/" wp-cli.spec || die 5 "Changleog update failed" + +# Create man page +{ + echo '.TH "WP" "1"' + php wp-cli.phar --help +} \ + | sed -e 's/^\([A-Z ]\+\)$/.SH "\1"/' \ + | sed -e 's/^ wp$/wp \\- The command line interface for WordPress/' \ + > wp.1 + +# Build the package +rpmbuild --define "_sourcedir ${PWD}" --define "_rpmdir ${PWD}" -bb wp-cli.spec | tee wp-cli-updaterpm-rpmbuild.$$.log + +rpm_path=`grep -o "/.*/noarch/wp-cli-.*noarch.rpm" wp-cli-updaterpm-rpmbuild.$$.log` + +rm -f wp-cli-updaterpm-rpmbuild.$$.log + +if [ ${#rpm_path} -lt 20 ] ; then + echo "RPM path doesn't exist ($rpm_path)" + exit +fi + +if [[ $(type -P "rpmlint") ]] ; then + echo "Using rpmlint to check for errors" +# Run linter +cat <<"EOF" > rpmlint.config +setOption("CompressExtension", "gz") +addFilter(": E: no-packager-tag") +addFilter(": E: no-signature") +addFilter(": E: no-dependency-on locales-cli") +EOF + + rpmlint -v -f rpmlint.config -i $rpm_path || true + +elif ([ $(type -P "rpm2cpio") ] && [ $(type -P "cpio") ]); then + echo "No RPM lint found $rpm_path .. using alternative method" + mkdir rpm-test-$$ + cd rpm-test-$$ + if [ $? -ne 0 ] ; then + echo "Failed to cd into rpm-test-$$" + exit; + fi + rpm2cpio $rpm_path | cpio -idmv + + if [ -f "usr/bin/wp" ] ; then + echo "RPM test succeeded" + else + echo "RPM test failed" + fi + rm -rfv ../rpm-test-$$ +else + echo "All test methods failed" +fi + + +popd > /dev/null + +echo "OK." diff --git a/utils/wp.fish b/utils/wp.fish deleted file mode 100644 index a3671ba1c..000000000 --- a/utils/wp.fish +++ /dev/null @@ -1,24 +0,0 @@ -# Fish completion for the `wp` command -# Check $fish_complete_path for possible install locations -# Or check the documentation: -# https://fishshell.com/docs/current/completions.html#where-to-put-completions - -function __wp_cli_complete - # Get current buffer and cursor - set --local COMP_LINE (commandline) - set --local COMP_POINT (commandline -C) - - # Get valid completions from wp-cli - set --local opts (wp cli completions --line=$COMP_LINE --point=$COMP_POINT) - - # wp-cli will indicate if it needs a file - if string match -qe " " -- $opts - command ls -1 - else - # Remove unnecessary double spaces that wp-cli splits options with - string trim -- $opts - # `string` echoes each result on a newline. - # Which is then collected for use with the `-a` flag for `complete`. - end -end -complete -f -a "(__wp_cli_complete)" wp