diff --git a/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md b/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md index 39422af0ae..c4181df8e9 100644 --- a/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md +++ b/.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md @@ -100,6 +100,7 @@ assignees: '' ``` 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 @@ -139,6 +140,7 @@ assignees: '' 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/) @@ -147,13 +149,13 @@ assignees: '' ``` $ wp cli update - You have version 1.4.0-alpha-88450b8. Would you like to update to 1.4.0? [y/n] y - Downloading from https://github.com/wp-cli/wp-cli/releases/download/v1.4.0/wp-cli-1.4.0.phar... - md5 hash verified: 179fc8dacbfe3ebc2d00ba57a333c982 + 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 1.4.0. + Success: Updated WP-CLI to 2.12.0. $ wp cli version - WP-CLI 2.8.1 + 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 ``` diff --git a/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md b/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md index 6872678ae5..e86ee350c5 100644 --- a/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md +++ b/.github/ISSUE_TEMPLATE/5-PATCH_RELEASE_CHECKLIST.md @@ -86,6 +86,7 @@ assignees: 'schlessera' ``` 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 ``` @@ -110,30 +111,33 @@ assignees: 'schlessera' ``` git status git add . - git commit -m "Update stable to v1.x.0" + 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-1.x.0.phar - cp wp-cli.phar.gpg wp-cli-1.x.0.phar.gpg - cp wp-cli.phar.asc wp-cli-1.x.0.phar.asc - cp wp-cli.phar.md5 wp-cli-1.x.0.phar.md5 - cp wp-cli.phar.sha512 wp-cli-1.x.0.phar.sha512 + 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 have version 1.4.0-alpha-88450b8. Would you like to update to 1.4.0? [y/n] y - Downloading from https://github.com/wp-cli/wp-cli/releases/download/v1.4.0/wp-cli-1.4.0.phar... - md5 hash verified: 179fc8dacbfe3ebc2d00ba57a333c982 + 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 1.4.0. - $ wp @daniel option get home - https://danielbachhuber.com + 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 @@ -174,6 +178,12 @@ assignees: 'schlessera' - [ ] 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 `0.24.0`, the version should be bumped to `0.25.0-alpha`. Doing so ensure `wp cli update --nightly` works as expected. + For instance, if the release version was `2.8.0`, the version should be bumped to `2.9.0-alpha`. -- [ ] 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-master"`. + 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/workflows/automerge.yml b/.github/workflows/automerge.yml deleted file mode 100644 index e1b9982838..0000000000 --- a/.github/workflows/automerge.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Automatic Merge -on: - workflow_dispatch: - schedule: - # https://crontab.guru/every-hour - - cron: 0 * * * * - -jobs: - merge: - name: Merge Pull Requests - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'wp-cli' }} - steps: - - name: Merge - uses: nucleos/auto-merge-action@1.3.0 - env: - "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} - with: - label: automerge diff --git a/.gitignore b/.gitignore index 2247c3e81d..5ad4029f17 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ phpcs.xml composer.lock .phpunit.result.cache .phpunit.cache +/build/logs diff --git a/README.md b/README.md index 2f408d48eb..f235cc8d80 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Ongoing maintenance is made possible by: -The current stable release is [version 2.11.0](https://make.wordpress.org/cli/2024/08/08/wp-cli-v2-11-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 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. [![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") @@ -92,7 +92,7 @@ WP_CLI phar path: 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.11.0 +WP-CLI version: 2.12.0 ``` ### Updating diff --git a/VERSION b/VERSION index ed0edc885b..3ca2c9b2cc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.11.0 \ No newline at end of file +2.12.0 \ No newline at end of file diff --git a/bundle/rmccue/requests/CHANGELOG.md b/bundle/rmccue/requests/CHANGELOG.md index 7f2f7c801b..22af108ede 100644 --- a/bundle/rmccue/requests/CHANGELOG.md +++ b/bundle/rmccue/requests/CHANGELOG.md @@ -1,6 +1,61 @@ Changelog ========= +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 ----- @@ -65,7 +120,7 @@ Changelog - 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] +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 @@ -983,6 +1038,7 @@ Initial release! [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 @@ -1000,8 +1056,10 @@ Initial release! [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 @@ -1023,6 +1081,7 @@ Initial release! [gh-TimothyBJacobs]: https://github.com/TimothyBJacobs [gh-tnorthcutt]: https://github.com/tnorthcutt [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 diff --git a/bundle/rmccue/requests/README.md b/bundle/rmccue/requests/README.md index eb198e1cda..756bc5321f 100644 --- a/bundle/rmccue/requests/README.md +++ b/bundle/rmccue/requests/README.md @@ -151,11 +151,29 @@ If you'd like to run a single set of tests, specify just the name: $ 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 +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. diff --git a/bundle/rmccue/requests/certificates/cacert.pem b/bundle/rmccue/requests/certificates/cacert.pem index 6b93dc34f8..86d6cd80cc 100644 --- a/bundle/rmccue/requests/certificates/cacert.pem +++ b/bundle/rmccue/requests/certificates/cacert.pem @@ -1,7 +1,7 @@ ## ## Bundle of CA Root Certificates ## -## Certificate data from Mozilla as of: Tue May 30 03:12:04 2023 GMT +## Certificate data from Mozilla as of: Tue Jul 2 03:12:04 2024 GMT ## ## This is a bundle of X.509 certificates of public Certificate Authorities ## (CA). These were automatically extracted from Mozilla's root certificates @@ -14,7 +14,7 @@ ## Just configure this file as the SSLCACertificateFile. ## ## Conversion done with mk-ca-bundle.pl version 1.29. -## SHA256: c47475103fb05bb562bbadff0d1e72346b03236154e1448a6ca191b740f83507 +## SHA256: 456ff095dde6dd73354c5c28c73d9c06f53b61a803963414cb91a1d92945cdd3 ## @@ -200,27 +200,6 @@ vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -Security Communication Root CA -============================== ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP -U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw -HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP -U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw -8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM -DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX -5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd -DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 -JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw -DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g -0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a -mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ -s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ -6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi -FL39vmwLAw== ------END CERTIFICATE----- - XRamp Global CA Root ==================== -----BEGIN CERTIFICATE----- @@ -669,39 +648,6 @@ YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r kpeDMdmztcpHWD9f -----END CERTIFICATE----- -Autoridad de Certificacion Firmaprofesional CIF A62634068 -========================================================= ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA -BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 -MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw -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+JIwuwIDAQABo4HvMIHsMBIGA1Ud -EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH -DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA -bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx -ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx -51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk -R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP -T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f -Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl -osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR -crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR -saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD -KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi -6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - Izenpe.com ========== -----BEGIN CERTIFICATE----- @@ -2654,36 +2600,6 @@ 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----- @@ -3222,55 +3138,6 @@ AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8 rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- -E-Tugra Global Root CA RSA v3 -============================= ------BEGIN CERTIFICATE----- -MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQELBQAwgYAxCzAJ -BgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAb -BgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290 -IENBIFJTQSB2MzAeFw0yMDAzMTgwOTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJU -UjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRF -LVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0Eg -djMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J77gnJY9LTQ91ew6aEOErx -jYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscxuj7X/iWpKo429NEvx7epXTPcMHD4QGxL -sqYxYdE0PD0xesevxKenhOGXpOhL9hd87jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF -/YP9f4RtNGx/ardLAQO/rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8q -QedmCeFLl+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bGwzrw -bMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4znKS4iicvObpCdg6 -04nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBOM/J+JjKsBY04pOZ2PJ8QaQ5tndLB -eSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiM -bIedBi3x7+PmBvrFZhNb/FAHnnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbg -h3cXTJ2w2AmoDVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSytK7mLfcm1ap1 -LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAImocn+M684uGMQQ -gC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN4 -38o2Fi+CiJ+8EUdPdk3ILY7r3y18Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/q -ln0F7psTpURs+APQ3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3s -SdPkvmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn99t2HVhjY -sCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQmhty3QUBjYZgv6Rn7rWl -DdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YAVSgU7NbHEqIbZULpkejLPoeJVF3Zr52X -nGnnCv8PWniLYypMfUeUP95L6VPQMPHF9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFH -IK+WEj5jlB0E5y67hscMmoi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiX -YY60MGo8bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ ------END CERTIFICATE----- - -E-Tugra Global Root CA ECC v3 -============================= ------BEGIN CERTIFICATE----- -MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMwgYAxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVncmEgRUJHIEEuUy4xHTAbBgNV -BAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENB -IEVDQyB2MzAeFw0yMDAzMTgwOTQ2NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEP -MA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 -Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQKczLWYHMjLiSF4mDKpL2 -w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YKfWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31 -Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQ -zPUwHQYDVR0OBBYEFP+CMXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO -PQQDAwNpADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/67W4W -Aie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFxvmjkI6TZraE3 ------END CERTIFICATE----- - Security Communication RootCA3 ============================== -----BEGIN CERTIFICATE----- @@ -3361,3 +3228,341 @@ SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== -----END CERTIFICATE----- + +Sectigo Public Server Authentication Root E46 +============================================= +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH +QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2 +ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5 +WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0 +aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr +gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0 +NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud +DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH +lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U +SAGKcw== +-----END CERTIFICATE----- + +Sectigo Public Server Authentication Root R46 +============================================= +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG +EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1 +OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k +1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf +GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP +FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu +ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz +Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A +wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF +plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ +EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW +6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI +IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp +E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4 +exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M +0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI +84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m +pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd +Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b +E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm +J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +SSL.com TLS RSA Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG +EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg +Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC +VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u +9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y +7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac +oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M +R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG +D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW +TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk +8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq +g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk +7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu +N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN +j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by +iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU +o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo +ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib +MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi +vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7 +P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0 +9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +SSL.com TLS ECC Root CA 2022 +============================ +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV +UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v +dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx +GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy +JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1 +5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7 +81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG +MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w +7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5 +Zn6g6g== +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA ECC TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB +dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD +VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg +VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT +AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K +DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS +b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX +NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+ +uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY +a3cpetskz2VAv9LcjBHo9H1/IISpQuQo +-----END CERTIFICATE----- + +Atos TrustedRoot Root CA RSA TLS 2021 +===================================== +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD +DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw +CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0 +b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV +BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB +l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG +vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK +ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt +0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK +PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY +sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY +Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+ +rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa +fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G +CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl +Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX +AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G +slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt +afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q +TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj +1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l +PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W +HYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +TrustAsia Global Root CA G3 +=========================== +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG +A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM +G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw +MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu +MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz +lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ +Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V +P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag +dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm +9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc +D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg +WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea +mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF +TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj +7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1 +D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T +G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj +duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl +cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys ++TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli +2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y +aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS +ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR +JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH +-----END CERTIFICATE----- + +TrustAsia Global Root CA G4 +=========================== +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE +BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry +dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa +MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw +IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8 +m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/ +pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA +bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk +dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx +eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot +6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2 +Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW +pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +CommScope Public Trust ECC Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE +BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz +dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT +AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg +RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M +MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE +SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9 +Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7 +3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-01 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft +nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6 +uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq +ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs +vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c +Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif +BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9 +lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo +KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH ++VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4 +5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM +3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck +jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf +Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W +NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+ +o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/ +oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc +1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM +6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +CommScope Public Trust RSA Root-02 +================================== +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG +A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU +cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV +BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1 +c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V +rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx +7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC +e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W +Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp +M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf +hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr +eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE +VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t +Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx +cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF +1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa +MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd +gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O +HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm +YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr +dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ +iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN +lM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- + +Telekom Security TLS ECC Root 2020 +================================== +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE +RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl +a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz +NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg +R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG +SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1 +2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC +MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ +Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU +ga/sf+Rn27iQ7t0l +-----END CERTIFICATE----- + +Telekom Security TLS RSA Root 2023 +================================== +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG +EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU +ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy +NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp +dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC +KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP +GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx +UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo +l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9 +FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v +zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg +rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML +KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S +WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+ +sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp +kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy +/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4 +mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz +aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa +oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8 +wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE +HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0 +o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +FIRMAPROFESIONAL CA ROOT-A WEB +============================== +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF +UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4 +MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2 +WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h +bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM +IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6 +iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg +st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD +Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB +/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL +cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ +pYXFuXqUPoeovQA= +-----END CERTIFICATE----- diff --git a/bundle/rmccue/requests/certificates/cacert.pem.sha256 b/bundle/rmccue/requests/certificates/cacert.pem.sha256 index 97537cf7a5..d2cba5fd06 100644 --- a/bundle/rmccue/requests/certificates/cacert.pem.sha256 +++ b/bundle/rmccue/requests/certificates/cacert.pem.sha256 @@ -1 +1 @@ -5fadcae90aa4ae041150f8e2d26c37d980522cdb49f923fc1e1b5eb8d74e71ad cacert.pem +1bf458412568e134a4514f5e170a328d11091e071c7110955c9884ed87972ac9 cacert.pem diff --git a/bundle/rmccue/requests/composer.json b/bundle/rmccue/requests/composer.json index 2ea9e6e4d8..2e1410a987 100644 --- a/bundle/rmccue/requests/composer.json +++ b/bundle/rmccue/requests/composer.json @@ -56,6 +56,12 @@ "yoast/phpunit-polyfills": "^1.0.0", "roave/security-advisories": "dev-latest" }, + "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" + }, "autoload": { "psr-4": { "WpOrg\\Requests\\": "src/" diff --git a/bundle/rmccue/requests/src/Cookie.php b/bundle/rmccue/requests/src/Cookie.php index 6f971d6dbf..2cc821d647 100644 --- a/bundle/rmccue/requests/src/Cookie.php +++ b/bundle/rmccue/requests/src/Cookie.php @@ -470,13 +470,19 @@ public static function parse($cookie_header, $name = '', $reference_time = null) * @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, Iri $origin = null, $time = null) { + 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); diff --git a/bundle/rmccue/requests/src/IdnaEncoder.php b/bundle/rmccue/requests/src/IdnaEncoder.php index 4257a1acbe..9f235527bf 100644 --- a/bundle/rmccue/requests/src/IdnaEncoder.php +++ b/bundle/rmccue/requests/src/IdnaEncoder.php @@ -216,18 +216,18 @@ protected static function utf8_to_codepoints($input) { } if (// Non-shortest form sequences are invalid - $length > 1 && $character <= 0x7F - || $length > 2 && $character <= 0x7FF - || $length > 3 && $character <= 0xFFFF + ($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 + || ($character >= 0xFDD0 && $character <= 0xFDEF) || ( // Everything else not in ucschar - $character > 0xD7FF && $character < 0xF900 + ($character > 0xD7FF && $character < 0xF900) || $character < 0x20 - || $character > 0x7E && $character < 0xA0 + || ($character > 0x7E && $character < 0xA0) || $character > 0xEFFFD ) ) { diff --git a/bundle/rmccue/requests/src/Ipv6.php b/bundle/rmccue/requests/src/Ipv6.php index a90ab8a831..bcdd63649f 100644 --- a/bundle/rmccue/requests/src/Ipv6.php +++ b/bundle/rmccue/requests/src/Ipv6.php @@ -161,7 +161,7 @@ public static function check_ipv6($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) { + 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 === '') { diff --git a/bundle/rmccue/requests/src/Requests.php b/bundle/rmccue/requests/src/Requests.php index ac6ff55f99..9e7f4f3ff3 100644 --- a/bundle/rmccue/requests/src/Requests.php +++ b/bundle/rmccue/requests/src/Requests.php @@ -148,7 +148,7 @@ class Requests { * * @var string */ - const VERSION = '2.0.7'; + const VERSION = '2.0.12'; /** * Selected transport name diff --git a/bundle/rmccue/requests/src/Transport/Fsockopen.php b/bundle/rmccue/requests/src/Transport/Fsockopen.php index 2b53d0c10c..6bd82a32f0 100644 --- a/bundle/rmccue/requests/src/Transport/Fsockopen.php +++ b/bundle/rmccue/requests/src/Transport/Fsockopen.php @@ -144,7 +144,15 @@ public function request($url, $headers = [], $data = [], $options = []) { $verifyname = false; } - stream_context_set_option($context, ['ssl' => $context_options]); + // 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+. + stream_context_set_options($context, ['ssl' => $context_options]); + } else { + // PHP < 8.3. + stream_context_set_option($context, ['ssl' => $context_options]); + } } else { $remote_socket = 'tcp://' . $host; } diff --git a/composer.json b/composer.json index aaefc0705a..0b2810ee58 100644 --- a/composer.json +++ b/composer.json @@ -10,18 +10,17 @@ "require": { "php": "^5.6 || ^7.0 || ^8.0", "ext-curl": "*", - "mustache/mustache": "^2.14.1", "symfony/finder": ">2.7", + "wp-cli/mustache": "^2.14.99", "wp-cli/mustangostang-spyc": "^0.6.3", - "wp-cli/php-cli-tools": "~0.11.2" + "wp-cli/php-cli-tools": "~0.12.4" }, "require-dev": { - "roave/security-advisories": "dev-latest", "wp-cli/db-command": "^1.3 || ^2", "wp-cli/entity-command": "^1.2 || ^2", "wp-cli/extension-command": "^1.1 || ^2", "wp-cli/package-command": "^1 || ^2", - "wp-cli/wp-cli-tests": "^4.0.1" + "wp-cli/wp-cli-tests": "^4.3.10" }, "suggest": { "ext-readline": "Include for a better --prompt implementation", @@ -33,11 +32,12 @@ "johnpbloch/wordpress-core-installer": true }, "process-timeout": 7200, - "sort-packages": true + "sort-packages": true, + "lock": false }, "extra": { "branch-alias": { - "dev-main": "2.11.x-dev" + "dev-main": "2.12.x-dev" } }, "autoload": { diff --git a/features/aliases.feature b/features/aliases.feature index 24b081fe43..4ad79bd9b5 100644 --- a/features/aliases.feature +++ b/features/aliases.feature @@ -207,16 +207,16 @@ Feature: Create shortcuts to specific WordPress installs Scenario: Add an alias Given a WP installation in 'foo' And a wp-cli.yml file: - """ - @foo: - ssh: wpcli@wp-cli.org:2222 - """ + """ + @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. - """ + """ + Success: Added '@dev' alias. + """ When I run `wp cli alias list` Then STDOUT should be YAML containing: """ @@ -230,31 +230,31 @@ Feature: Create shortcuts to specific WordPress installs When I try `wp cli alias add @something --config=project` Then STDERR should be: - """ - Error: No valid arguments passed. - """ + """ + 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. - """ + """ + 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'. - """ + """ + 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 - """ + """ + @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: @@ -276,28 +276,28 @@ Feature: Create shortcuts to specific WordPress installs When I try `wp cli alias update @foo` Then STDERR should be: - """ - Error: No valid arguments passed. - """ + """ + 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 - """ + """ + @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: """ @@ -336,9 +336,9 @@ Feature: Create shortcuts to specific WordPress installs When I try `wp cli alias update @foo --set-path=/new/path` Then STDOUT should be: - """ - Success: Updated '@foo' alias. - """ + """ + Success: Updated '@foo' alias. + """ When I run `wp cli alias list` Then STDOUT should be YAML containing: diff --git a/features/bootstrap.feature b/features/bootstrap.feature index 18d1c78fcc..2ba095956c 100644 --- a/features/bootstrap.feature +++ b/features/bootstrap.feature @@ -60,12 +60,12 @@ Feature: Bootstrap WP-CLI return; } // Override bundled command. - WP_CLI::add_command( 'eval', 'Eval_Command', array( 'when' => 'before_wp_load' ) ); + WP_CLI::add_command( 'eval', 'Custom_Eval_Command', array( 'when' => 'before_wp_load' ) ); """ - And a override/src/Eval_Command.php file: + And a override/src/Custom_Eval_Command.php file: """ 'before_wp_load' ) ); + WP_CLI::add_command( 'cli', 'Custom_CLI_Command', array( 'when' => 'before_wp_load' ) ); // Override bundled command. - WP_CLI::add_command( 'eval', 'Eval_Command', array( 'when' => 'before_wp_load' ) ); + 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' ) ); + } + } + ); """ - And a override/src/CLI_Command.php file: + And a override/src/Custom_CLI_Command.php file: """ 'before_wp_load' ) ); + WP_CLI::add_command( 'cli', 'Custom_CLI_Command', array( 'when' => 'before_wp_load' ) ); // Override bundled command. - WP_CLI::add_command( 'eval', 'Eval_Command', array( 'when' => 'before_wp_load' ) ); + 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' ) ); + } + } + ); """ - And a override/src/CLI_Command.php file: + And a override/src/Custom_CLI_Command.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! + """ diff --git a/features/class-wp-cli.feature b/features/class-wp-cli.feature index 190d65ab7d..07c01b8010 100644 --- a/features/class-wp-cli.feature +++ b/features/class-wp-cli.feature @@ -13,5 +13,3 @@ Feature: Various utilities for WP-CLI commands | func | | proc_open | | proc_close | - - diff --git a/features/cli-bash-completion.feature b/features/cli-bash-completion.feature index eb2667973d..340b265518 100644 --- a/features/cli-bash-completion.feature +++ b/features/cli-bash-completion.feature @@ -324,7 +324,7 @@ Feature: `wp cli completions` tasks """ --prompt= """ - Then STDOUT should not contain: + And STDOUT should not contain: """ --path """ diff --git a/features/cli-cache.feature b/features/cli-cache.feature index c2cd0b4a56..28f8e1a83c 100644 --- a/features/cli-cache.feature +++ b/features/cli-cache.feature @@ -37,7 +37,6 @@ Feature: CLI Cache When I run `wp --require=env-var.php core download --path=/tmp/wp-core --version=4.9 --force` Then STDERR should be empty - Scenario: Remove all but newest files from cache directory Given an empty cache And a file-a-12345.tmp cache file: diff --git a/features/cli-check-update.feature b/features/cli-check-update.feature new file mode 100644 index 0000000000..7e460080e8 --- /dev/null +++ b/features/cli-check-update.feature @@ -0,0 +1,157 @@ +Feature: Check for updates + + Scenario: Ignores updates with a higher PHP version requirement + Given that HTTP requests to https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100 will respond with: + """ + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978", + "assets_url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/assets", + "upload_url": "https://uploads.github.com/repos/wp-cli/wp-cli/releases/169243978/assets{?name,label}", + "html_url": "https://github.com/wp-cli/wp-cli/releases/tag/v999.9.9", + "id": 169243978, + "node_id": "RE_kwDOACQFs84KFnVK", + "tag_name": "v999.9.9", + "target_commitish": "main", + "name": "Version 999.9.9", + "draft": false, + "prerelease": false, + "created_at": "2024-08-08T03:04:55Z", + "published_at": "2024-08-08T03:51:13Z", + "assets": [ + { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/assets/184590231", + "id": 184590231, + "node_id": "RA_kwDOACQFs84LAJ-X", + "name": "wp-cli-999.9.9.phar", + "label": null, + "content_type": "application/octet-stream", + "state": "uploaded", + "size": 7048108, + "download_count": 722639, + "created_at": "2024-08-08T03:51:05Z", + "updated_at": "2024-08-08T03:51:08Z", + "browser_download_url": "https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.phar" + }, + { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/assets/184590231", + "id": 184590231, + "node_id": "RA_kwDOACQFs84LAJ-X", + "name": "wp-cli-999.9.9.phar", + "label": null, + "content_type": "application/octet-stream", + "state": "uploaded", + "size": 7048108, + "download_count": 722639, + "created_at": "2024-08-08T03:51:05Z", + "updated_at": "2024-08-08T03:51:08Z", + "browser_download_url": "https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.manifest.json" + } + ], + "tarball_url": "https://api.github.com/repos/wp-cli/wp-cli/tarball/v999.9.9", + "zipball_url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/v999.9.9", + "body": "- Allow manually dispatching tests workflow [[#5965](https://github.com/wp-cli/wp-cli/pull/5965)]\r\n- Add fish shell completion [[#5954](https://github.com/wp-cli/wp-cli/pull/5954)]\r\n- Add defaults and accepted values for runcommand() options in doc [[#5953](https://github.com/wp-cli/wp-cli/pull/5953)]\r\n- Address warnings with filenames ending in fullstop on Windows [[#5951](https://github.com/wp-cli/wp-cli/pull/5951)]\r\n- Fix unit tests [[#5950](https://github.com/wp-cli/wp-cli/pull/5950)]\r\n- Update copyright year in license [[#5942](https://github.com/wp-cli/wp-cli/pull/5942)]\r\n- Fix breaking multi-line CSV values on reading [[#5939](https://github.com/wp-cli/wp-cli/pull/5939)]\r\n- Fix broken Gutenberg test [[#5938](https://github.com/wp-cli/wp-cli/pull/5938)]\r\n- Update docker runner to resolve docker path using `/usr/bin/env` [[#5936](https://github.com/wp-cli/wp-cli/pull/5936)]\r\n- Fix `inherit` path in nested directory [[#5930](https://github.com/wp-cli/wp-cli/pull/5930)]\r\n- Minor docblock improvements [[#5929](https://github.com/wp-cli/wp-cli/pull/5929)]\r\n- Add Signup fetcher [[#5926](https://github.com/wp-cli/wp-cli/pull/5926)]\r\n- Ensure the alias has the leading `@` symbol when added [[#5924](https://github.com/wp-cli/wp-cli/pull/5924)]\r\n- Include any non default hook information in CompositeCommand [[#5921](https://github.com/wp-cli/wp-cli/pull/5921)]\r\n- Correct completion case when ends in = [[#5913](https://github.com/wp-cli/wp-cli/pull/5913)]\r\n- Docs: Fixes for inline comments [[#5912](https://github.com/wp-cli/wp-cli/pull/5912)]\r\n- Update Inline comments [[#5910](https://github.com/wp-cli/wp-cli/pull/5910)]\r\n- Add a real-world example for `wp cli has-command` [[#5908](https://github.com/wp-cli/wp-cli/pull/5908)]\r\n- Fix typos [[#5901](https://github.com/wp-cli/wp-cli/pull/5901)]\r\n- Avoid PHP deprecation notices in PHP 8.1.x [[#5899](https://github.com/wp-cli/wp-cli/pull/5899)]", + "reactions": { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/reactions", + "total_count": 9, + "+1": 4, + "-1": 0, + "laugh": 0, + "hooray": 1, + "confused": 0, + "heart": 0, + "rocket": 4, + "eyes": 0 + } + }, + { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978", + "assets_url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/assets", + "upload_url": "https://uploads.github.com/repos/wp-cli/wp-cli/releases/169243978/assets{?name,label}", + "html_url": "https://github.com/wp-cli/wp-cli/releases/tag/v777.7.7", + "id": 169243978, + "node_id": "RE_kwDOACQFs84KFnVK", + "tag_name": "v777.7.7", + "target_commitish": "main", + "name": "Version 777.7.7", + "draft": false, + "prerelease": false, + "created_at": "2024-08-08T03:04:55Z", + "published_at": "2024-08-08T03:51:13Z", + "assets": [ + { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/assets/184590231", + "id": 184590231, + "node_id": "RA_kwDOACQFs84LAJ-X", + "name": "wp-cli-777.7.7.phar", + "label": null, + "content_type": "application/octet-stream", + "state": "uploaded", + "size": 7048108, + "download_count": 722639, + "created_at": "2024-08-08T03:51:05Z", + "updated_at": "2024-08-08T03:51:08Z", + "browser_download_url": "https://github.com/wp-cli/wp-cli/releases/download/v777.7.7/wp-cli-777.7.7.phar" + }, + { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/assets/184590231", + "id": 184590231, + "node_id": "RA_kwDOACQFs84LAJ-X", + "name": "wp-cli-777.7.7.phar", + "label": null, + "content_type": "application/octet-stream", + "state": "uploaded", + "size": 7048108, + "download_count": 722639, + "created_at": "2024-08-08T03:51:05Z", + "updated_at": "2024-08-08T03:51:08Z", + "browser_download_url": "https://github.com/wp-cli/wp-cli/releases/download/v777.7.7/wp-cli-777.7.7.manifest.json" + } + ], + "tarball_url": "https://api.github.com/repos/wp-cli/wp-cli/tarball/v777.7.7", + "zipball_url": "https://api.github.com/repos/wp-cli/wp-cli/zipball/v777.7.7", + "body": "- Allow manually dispatching tests workflow [[#5965](https://github.com/wp-cli/wp-cli/pull/5965)]\r\n- Add fish shell completion [[#5954](https://github.com/wp-cli/wp-cli/pull/5954)]\r\n- Add defaults and accepted values for runcommand() options in doc [[#5953](https://github.com/wp-cli/wp-cli/pull/5953)]\r\n- Address warnings with filenames ending in fullstop on Windows [[#5951](https://github.com/wp-cli/wp-cli/pull/5951)]\r\n- Fix unit tests [[#5950](https://github.com/wp-cli/wp-cli/pull/5950)]\r\n- Update copyright year in license [[#5942](https://github.com/wp-cli/wp-cli/pull/5942)]\r\n- Fix breaking multi-line CSV values on reading [[#5939](https://github.com/wp-cli/wp-cli/pull/5939)]\r\n- Fix broken Gutenberg test [[#5938](https://github.com/wp-cli/wp-cli/pull/5938)]\r\n- Update docker runner to resolve docker path using `/usr/bin/env` [[#5936](https://github.com/wp-cli/wp-cli/pull/5936)]\r\n- Fix `inherit` path in nested directory [[#5930](https://github.com/wp-cli/wp-cli/pull/5930)]\r\n- Minor docblock improvements [[#5929](https://github.com/wp-cli/wp-cli/pull/5929)]\r\n- Add Signup fetcher [[#5926](https://github.com/wp-cli/wp-cli/pull/5926)]\r\n- Ensure the alias has the leading `@` symbol when added [[#5924](https://github.com/wp-cli/wp-cli/pull/5924)]\r\n- Include any non default hook information in CompositeCommand [[#5921](https://github.com/wp-cli/wp-cli/pull/5921)]\r\n- Correct completion case when ends in = [[#5913](https://github.com/wp-cli/wp-cli/pull/5913)]\r\n- Docs: Fixes for inline comments [[#5912](https://github.com/wp-cli/wp-cli/pull/5912)]\r\n- Update Inline comments [[#5910](https://github.com/wp-cli/wp-cli/pull/5910)]\r\n- Add a real-world example for `wp cli has-command` [[#5908](https://github.com/wp-cli/wp-cli/pull/5908)]\r\n- Fix typos [[#5901](https://github.com/wp-cli/wp-cli/pull/5901)]\r\n- Avoid PHP deprecation notices in PHP 8.1.x [[#5899](https://github.com/wp-cli/wp-cli/pull/5899)]", + "reactions": { + "url": "https://api.github.com/repos/wp-cli/wp-cli/releases/169243978/reactions", + "total_count": 9, + "+1": 4, + "-1": 0, + "laugh": 0, + "hooray": 1, + "confused": 0, + "heart": 0, + "rocket": 4, + "eyes": 0 + } + } + ] + """ + + And that HTTP requests to wp-cli-999.9.9.manifest.json will respond with: + """ + HTTP/1.1 200 + Content-Type: application/json + + { + "requires_php": "123.4.5" + } + """ + + And that HTTP requests to wp-cli-777.7.7.manifest.json will respond with: + """ + HTTP/1.1 200 + Content-Type: application/json + + { + "requires_php": "5.6.0" + } + """ + + When I run `wp cli check-update` + Then STDOUT should be a table containing rows: + | version | update_type | package_url | status | requires_php | + | 999.9.9 | major | https://github.com/wp-cli/wp-cli/releases/download/v999.9.9/wp-cli-999.9.9.phar | unavailable | 123.4.5 | + | 777.7.7 | major | https://github.com/wp-cli/wp-cli/releases/download/v777.7.7/wp-cli-777.7.7.phar | available | 5.6.0 | diff --git a/features/command.feature b/features/command.feature index 57e1629068..2629880fdf 100644 --- a/features/command.feature +++ b/features/command.feature @@ -1515,9 +1515,9 @@ Feature: WP-CLI Commands # TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9 When I try `wp custom --help` Then STDOUT should contain: - """ - wp custom - """ + """ + wp custom + """ Scenario: subcommand alias should respect @when definition Given an empty directory diff --git a/features/config.feature b/features/config.feature index 5b3f7f723a..10273ea8fa 100644 --- a/features/config.feature +++ b/features/config.feature @@ -65,17 +65,17 @@ Feature: Have a config file Scenario: WP in a subdirectory (autodetected) Given a WP installation in 'foo' - Given an index.php file: - """ - require('./foo/wp-blog-header.php'); - """ + And 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 @@ -239,7 +239,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 @@ -249,7 +249,7 @@ Feature: Have a config file """ No readable global config found """ - Then STDERR should contain: + And STDERR should contain: """ No project config found """ @@ -276,7 +276,7 @@ Feature: Have a config file """ No readable global config found """ - Then STDERR should contain: + And STDERR should contain: """ No project config found """ @@ -303,7 +303,7 @@ Feature: Have a config file """ No readable global config found """ - Then STDERR should not contain: + And STDERR should not contain: """ No project config found """ @@ -328,10 +328,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: @@ -625,3 +625,117 @@ Feature: Have a config file """ 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: + """ + 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 | diff --git a/features/hook.feature b/features/hook.feature index 2e3793873c..4483b59591 100644 --- a/features/hook.feature +++ b/features/hook.feature @@ -147,7 +147,7 @@ Feature: Tests `WP_CLI::add_hook()` """ `add_hook()` to the `custom_hook` is working. """ - Then STDOUT should not contain: + And STDOUT should not contain: """ First argument is not being passed in to callback properly """ @@ -204,7 +204,7 @@ Feature: Tests `WP_CLI::add_hook()` """ `add_hook()` to the `custom_hook` is working. """ - Then STDOUT should not contain: + And STDOUT should not contain: """ First argument is not being passed in to callback properly """ diff --git a/features/requests.feature b/features/requests.feature index 50260c7735..8ef03e75d1 100644 --- a/features/requests.feature +++ b/features/requests.feature @@ -1,7 +1,8 @@ Feature: Requests integration with both v1 and v2 # This test downgrades to WordPress 5.8, but the SQLite plugin requires 6.0+ - @require-mysql + # 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: @@ -70,14 +71,18 @@ Feature: Requests integration with both v1 and v2 """ And STDERR should be empty - When I run `wp plugin install duplicate-post` + When I run `wp plugin install debug-bar` Then STDOUT should contain: """ Success: Installed 1 of 1 plugins. """ - Scenario: Current version with WordPress-bundled Requests v2 + 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` @@ -97,7 +102,7 @@ Feature: Requests integration with both v1 and v2 """ And STDERR should be empty - When I run `wp plugin install duplicate-post` + When I run `wp plugin install debug-bar` Then STDOUT should contain: """ Success: Installed 1 of 1 plugins. diff --git a/features/runcommand.feature b/features/runcommand.feature index 7932f730f0..1a7c6de26b 100644 --- a/features/runcommand.feature +++ b/features/runcommand.feature @@ -235,7 +235,7 @@ Feature: Run a WP-CLI command | --no-launch | | --launch | - @less-than-php-8 + @less-than-php-8 Scenario Outline: Installed packages work as expected Given a WP installation @@ -249,9 +249,9 @@ 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' @@ -266,9 +266,9 @@ Feature: Run a WP-CLI command And the return code should be 0 Examples: - | flag | - | --no-launch | - | --launch | + | flag | + | --no-launch | + | --launch | Scenario Outline: Apply backwards compat conversions Given a WP installation @@ -283,9 +283,9 @@ Feature: Run a WP-CLI command And the return code should be 0 Examples: - | flag | - | --no-launch | - | --launch | + | flag | + | --no-launch | + | --launch | Scenario Outline: Check that proc_open() and proc_close() aren't disabled for launch Given a WP installation @@ -338,3 +338,52 @@ Feature: Run a WP-CLI command """ The used path is: /bad/path/ """ + + Scenario: Check that required files are used from command arguments and ENV VAR + Given a WP installation + And a custom-cmd.php file: + """ + ' 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 diff --git a/features/skip-themes.feature b/features/skip-themes.feature index c04599f41c..1246b7c681 100644 --- a/features/skip-themes.feature +++ b/features/skip-themes.feature @@ -59,7 +59,7 @@ Feature: Skipping themes And I run `wp theme install moina moina-blog` When I run `wp theme activate moina` - When I run `wp eval 'var_export( function_exists( "moina_setup" ) );'` + And I run `wp eval 'var_export( function_exists( "moina_setup" ) );'` Then STDOUT should be: """ true @@ -72,9 +72,8 @@ Feature: Skipping themes """ And STDERR should be empty - When I run `wp theme activate moina-blog` - When I run `wp eval 'var_export( function_exists( "moina_setup" ) );'` + And I run `wp eval 'var_export( function_exists( "moina_setup" ) );'` Then STDOUT should be: """ true diff --git a/features/steps.feature b/features/steps.feature index 97c5048a1e..6adcb2c431 100644 --- a/features/steps.feature +++ b/features/steps.feature @@ -17,12 +17,12 @@ Feature: Make sure "Given", "When", "Then" steps work as expected # Note this would give behat "undefined step" message as "save" step uses "\w+" #And save STDOUT as {VARIABLE_NAME_WITH_PERCENT_%} - When I run `echo {VARIABLE_NAME}` + And I run `echo {VARIABLE_NAME}` Then STDOUT should match /^value$/ And STDOUT should be: - """ - value - """ + """ + value + """ When I run `echo {V}` Then STDOUT should match /^value$/ @@ -45,9 +45,9 @@ Feature: Make sure "Given", "When", "Then" steps work as expected When I run `echo {2_VARIABLE_NAME_STARTING_WITH_DIGIT}` Then STDOUT should match /^\{2_VARIABLE_NAME_STARTING_WITH_DIGIT}$/ And STDOUT should contain: - """ - { - """ + """ + { + """ When I run `echo {2}` Then STDOUT should match /^\{2}$/ diff --git a/features/utils-wp.feature b/features/utils-wp.feature index 6df8f8e3d5..9c3979a2f0 100644 --- a/features/utils-wp.feature +++ b/features/utils-wp.feature @@ -113,7 +113,7 @@ Feature: Utilities that depend on WordPress code wp_term_taxonomy """ # Leave out wp_termmeta for old WP compat. - But STDOUT should contain: + And STDOUT should contain: """ wp_terms wp_usermeta @@ -538,7 +538,7 @@ Feature: Utilities that depend on WordPress code """ # 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: + And STDOUT should contain: """ wp_blogs wp_categories diff --git a/features/utils.feature b/features/utils.feature index aac2114dca..bde10c3871 100644 --- a/features/utils.feature +++ b/features/utils.feature @@ -31,7 +31,7 @@ Feature: Utilities that do NOT depend on WordPress code @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( "/usr/bin/env mysql --no-defaults", [ "user" => "{DB_USER}", "pass" => "{DB_PASSWORD}", "host" => "{DB_HOST}", "execute" => "SHOW DATABASES;" ] );'` + 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 @@ -42,7 +42,7 @@ Feature: Utilities that do NOT depend on WordPress code """ And STDERR should be empty - When I try `wp --skip-wordpress eval 'WP_CLI\Utils\run_mysql_command( "/usr/bin/env mysql --no-defaults", [ "user" => "{DB_USER}", "pass" => "{DB_PASSWORD}", "host" => "{DB_HOST}", "execute" => "broken query" ]);'` + 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: """ @@ -51,7 +51,7 @@ Feature: Utilities that do NOT depend on WordPress code @require-mysql Scenario: Check that `Utils\run_mysql_command()` can return data and errors if requested - When I run `wp --skip-wordpress eval 'list( $stdout, $stderr, $exit_code ) = WP_CLI\Utils\run_mysql_command( "/usr/bin/env mysql --no-defaults", [ "user" => "{DB_USER}", "pass" => "{DB_PASSWORD}", "host" => "{DB_HOST}", "execute" => "SHOW DATABASES;" ], null, false ); fwrite( STDOUT, strtoupper( $stdout ) ); fwrite( STDERR, strtoupper( $stderr ) );'` + 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 @@ -70,7 +70,7 @@ Feature: Utilities that do NOT depend on WordPress code """ And STDERR should be empty - When I try `wp --skip-wordpress eval 'list( $stdout, $stderr, $exit_code ) = WP_CLI\Utils\run_mysql_command( "/usr/bin/env mysql --no-defaults", [ "user" => "{DB_USER}", "pass" => "{DB_PASSWORD}", "host" => "{DB_HOST}", "execute" => "broken query" ], null, false ); fwrite( STDOUT, strtoupper( $stdout ) ); fwrite( STDERR, strtoupper( $stderr ) );'` + 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: """ @@ -149,19 +149,19 @@ Feature: Utilities that do NOT depend on WordPress code """ And save STDOUT as {DB_HOST_STRING} - When I try `mysql --database={DB_NAME} --user={DB_ROOT_USER} --password={DB_ROOT_PASSWORD} {DB_HOST_STRING} -e "SET GLOBAL max_allowed_packet=64*1024*1024;"` + 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 --database={DB_NAME} --user={DB_USER} --password={DB_PASSWORD} {DB_HOST_STRING} < test_db.sql` + 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 `mysqldump --help | grep -q 'column-statistics' && echo '--skip-column-statistics'` + 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=50M -ddisable_functions=ini_set} eval '\WP_CLI\Utils\run_mysql_command("/usr/bin/env mysqldump {SKIP_COLUMN_STATISTICS_FLAG} --no-tablespaces {DB_NAME}", [ "user" => "{DB_USER}", "pass" => "{DB_PASSWORD}", "host" => "{DB_HOST}" ], null, true);'` + 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: diff --git a/features/validation.feature b/features/validation.feature index 15e9c040db..ab8f09363f 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 - Then STDOUT should contain: + And STDOUT should contain: """ usage: wp plugin install """ diff --git a/features/wp-config.feature b/features/wp-config.feature index fc288721ff..e14d7642b3 100644 --- a/features/wp-config.feature +++ b/features/wp-config.feature @@ -22,7 +22,7 @@ Feature: wp-config """ When I try `wp eval "echo 'TEST_CONFIG_OVERRIDE => ' . TEST_CONFIG_OVERRIDE;"` - And STDERR should contain: + Then STDERR should contain: """ TEST_CONFIG_OVERRIDE """ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000000..cc1a0c2da8 --- /dev/null +++ b/manifest.json @@ -0,0 +1,3 @@ +{ + "requires_php": "5.6.0" +} diff --git a/php/WP_CLI/Bootstrap/CheckRoot.php b/php/WP_CLI/Bootstrap/CheckRoot.php new file mode 100644 index 0000000000..64c4779b20 --- /dev/null +++ b/php/WP_CLI/Bootstrap/CheckRoot.php @@ -0,0 +1,70 @@ +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; + } + + $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" + ); + } +} diff --git a/php/WP_CLI/Bootstrap/ConfigureRunner.php b/php/WP_CLI/Bootstrap/ConfigureRunner.php index 463a29a1ce..6bb65511dd 100644 --- a/php/WP_CLI/Bootstrap/ConfigureRunner.php +++ b/php/WP_CLI/Bootstrap/ConfigureRunner.php @@ -22,6 +22,10 @@ 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/Configurator.php b/php/WP_CLI/Configurator.php index 2438ea8311..47dad255b0 100644 --- a/php/WP_CLI/Configurator.php +++ b/php/WP_CLI/Configurator.php @@ -84,6 +84,17 @@ public function __construct( $path ) { $this->config[ $key ] = $details['default']; } + + $env_files = getenv( 'WP_CLI_REQUIRE' ) + ? array_filter( array_map( 'trim', explode( ',', 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'] ) ); + } } /** @@ -148,8 +159,8 @@ public function get_aliases() { /** * Splits a list of arguments into positional, associative and config. * - * @param array(string) $arguments - * @return array(array) + * @param array $arguments + * @return array> */ public function parse_args( $arguments ) { list( $positional_args, $mixed_args, $global_assoc, $local_assoc ) = self::extract_assoc( $arguments ); @@ -160,8 +171,8 @@ public function parse_args( $arguments ) { /** * Splits positional args from associative args. * - * @param array $arguments - * @return array(array) + * @param array $arguments + * @return array{0: array, 1: array, 2: array, 3: array} */ public static function extract_assoc( $arguments ) { $positional_args = []; diff --git a/php/WP_CLI/Dispatcher/CommandFactory.php b/php/WP_CLI/Dispatcher/CommandFactory.php index 0d720576b1..9b577224fe 100644 --- a/php/WP_CLI/Dispatcher/CommandFactory.php +++ b/php/WP_CLI/Dispatcher/CommandFactory.php @@ -23,8 +23,8 @@ class CommandFactory { /** * Create a new CompositeCommand (or Subcommand if class has __invoke()) * - * @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 string $name Represents how the command should be invoked + * @param string|callable-string|callable|array $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 ) { diff --git a/php/WP_CLI/Dispatcher/CompositeCommand.php b/php/WP_CLI/Dispatcher/CompositeCommand.php index c8408fd453..f69ed009b4 100644 --- a/php/WP_CLI/Dispatcher/CompositeCommand.php +++ b/php/WP_CLI/Dispatcher/CompositeCommand.php @@ -277,7 +277,7 @@ private static function get_aliases( $subcommands ) { /** * Composite commands can only be known by one name. * - * @return false + * @return string|false */ public function get_alias() { return false; diff --git a/php/WP_CLI/Exception/NonExistentKeyException.php b/php/WP_CLI/Exception/NonExistentKeyException.php index 7ede509e65..fa994eedbb 100644 --- a/php/WP_CLI/Exception/NonExistentKeyException.php +++ b/php/WP_CLI/Exception/NonExistentKeyException.php @@ -3,6 +3,7 @@ namespace WP_CLI\Exception; use OutOfBoundsException; +use WP_CLI\Traverser\RecursiveDataStructureTraverser; class NonExistentKeyException extends OutOfBoundsException { /** @var RecursiveDataStructureTraverser */ diff --git a/php/WP_CLI/Extractor.php b/php/WP_CLI/Extractor.php index b17b501f26..54332d468b 100644 --- a/php/WP_CLI/Extractor.php +++ b/php/WP_CLI/Extractor.php @@ -48,7 +48,7 @@ private static function extract_zip( $zipfile, $dest ) { throw new Exception( "Could not create folder '{$dest}'." ); } - if ( ! file( $zipfile ) + if ( ! file_exists( $zipfile ) || ! is_readable( $zipfile ) || filesize( $zipfile ) <= 0 ) { throw new Exception( "Invalid zip file '{$zipfile}'." ); @@ -127,7 +127,7 @@ private static function extract_tarball( $tarball, $dest ) { $tarball = "./{$tarball}"; } - if ( ! file( $tarball ) + if ( ! file_exists( $tarball ) || ! is_readable( $tarball ) || filesize( $tarball ) <= 0 ) { throw new Exception( "Invalid zip file '{$tarball}'." ); @@ -280,7 +280,7 @@ public static function zip_error_msg( $error_code ) { /** * Return formatted error message from ProcessRun of tar command. * - * @param Processrun $process_run + * @param ProcessRun $process_run * @return string|int The error message of the process, if available; * otherwise the return code. */ diff --git a/php/WP_CLI/Fetchers/Base.php b/php/WP_CLI/Fetchers/Base.php index aa66a6f6ef..8cbcd7640d 100644 --- a/php/WP_CLI/Fetchers/Base.php +++ b/php/WP_CLI/Fetchers/Base.php @@ -18,7 +18,7 @@ abstract class Base { protected $msg; /** - * @param string $arg The raw CLI argument. + * @param string|int $arg The raw CLI argument. * @return mixed|false The item if found; false otherwise. */ abstract public function get( $arg ); diff --git a/php/WP_CLI/Fetchers/Signup.php b/php/WP_CLI/Fetchers/Signup.php index 2e68e72c92..c981f6acf0 100644 --- a/php/WP_CLI/Fetchers/Signup.php +++ b/php/WP_CLI/Fetchers/Signup.php @@ -18,7 +18,7 @@ class Signup extends Base { * Get a signup. * * @param int|string $signup - * @return stdClass|false + * @return object|false */ public function get( $signup ) { return $this->get_signup( $signup ); @@ -28,7 +28,7 @@ public function get( $signup ) { * Get a signup by one of its identifying attributes. * * @param string $arg The raw CLI argument. - * @return stdClass|false The item if found; false otherwise. + * @return object|false The item if found; false otherwise. */ protected function get_signup( $arg ) { global $wpdb; diff --git a/php/WP_CLI/Fetchers/Site.php b/php/WP_CLI/Fetchers/Site.php index d777ed7127..2977f49363 100644 --- a/php/WP_CLI/Fetchers/Site.php +++ b/php/WP_CLI/Fetchers/Site.php @@ -27,6 +27,8 @@ public function get( $site_id ) { /** * Get site (blog) data for a given id. * + * @global wpdb $wpdb WordPress database abstraction object. + * * @param string $arg The raw CLI argument. * @return array|false The item if found; false otherwise. */ diff --git a/php/WP_CLI/FileCache.php b/php/WP_CLI/FileCache.php index cfc6480e14..d6c58c989a 100644 --- a/php/WP_CLI/FileCache.php +++ b/php/WP_CLI/FileCache.php @@ -328,7 +328,7 @@ protected function ensure_dir_exists( $dir ) { if ( ! @mkdir( $dir, 0777, true ) ) { $message = "Failed to create directory '{$dir}'"; $error = error_get_last(); - if ( is_array( $error ) && array_key_exists( 'message', $error ) ) { + if ( is_array( $error ) ) { $message .= ": {$error['message']}"; } WP_CLI::warning( "{$message}." ); diff --git a/php/WP_CLI/Formatter.php b/php/WP_CLI/Formatter.php index 32dd0c2abd..11c2f7b0bc 100644 --- a/php/WP_CLI/Formatter.php +++ b/php/WP_CLI/Formatter.php @@ -286,7 +286,6 @@ private function show_multiple_fields( $data, $format, $ascii_pre_colorized = fa default: WP_CLI::error( 'Invalid format: ' . $format ); - break; } } diff --git a/php/WP_CLI/Inflector.php b/php/WP_CLI/Inflector.php index 9e7420cdd9..872789448f 100644 --- a/php/WP_CLI/Inflector.php +++ b/php/WP_CLI/Inflector.php @@ -491,6 +491,10 @@ public static function pluralize( $word ) { return self::$cache['pluralize'][ $word ]; } } + + // Just so a string is always returned. + // This should never be reached. + return $word; } /** diff --git a/php/WP_CLI/Iterators/CSV.php b/php/WP_CLI/Iterators/CSV.php index 3551aa9a5d..d6688b5b75 100644 --- a/php/WP_CLI/Iterators/CSV.php +++ b/php/WP_CLI/Iterators/CSV.php @@ -4,6 +4,7 @@ use Countable; use Iterator; +use ReturnTypeWillChange; use SplFileObject; use WP_CLI; @@ -33,28 +34,32 @@ public function __construct( $filename, $delimiter = ',' ) { $this->delimiter = $delimiter; } + #[ReturnTypeWillChange] public function rewind() { rewind( $this->file_pointer ); - $this->columns = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter ); + $this->columns = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter, '"', '\\' ); $this->current_index = -1; $this->next(); } + #[ReturnTypeWillChange] public function current() { return $this->current_element; } + #[ReturnTypeWillChange] public function key() { return $this->current_index; } + #[ReturnTypeWillChange] public function next() { $this->current_element = false; while ( true ) { - $row = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter ); + $row = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter, '"', '\\' ); if ( false === $row ) { break; @@ -76,12 +81,14 @@ public function next() { } } + #[ReturnTypeWillChange] public function count() { $file = new SplFileObject( $this->filename, 'r' ); $file->seek( PHP_INT_MAX ); return $file->key() + 1; } + #[ReturnTypeWillChange] public function valid() { return is_array( $this->current_element ); } diff --git a/php/WP_CLI/Process.php b/php/WP_CLI/Process.php index 9a6c25986a..0702249533 100644 --- a/php/WP_CLI/Process.php +++ b/php/WP_CLI/Process.php @@ -43,9 +43,9 @@ class Process { public static $run_times = []; /** - * @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. + * @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. * * @return Process */ diff --git a/php/WP_CLI/RequestsLibrary.php b/php/WP_CLI/RequestsLibrary.php index a660acdb21..4fb1cf82a9 100644 --- a/php/WP_CLI/RequestsLibrary.php +++ b/php/WP_CLI/RequestsLibrary.php @@ -164,7 +164,6 @@ public static function set_version( $version ) { * Get the current class name. * * @return string The current class name. - * @throws RuntimeException if the class name is not set. */ public static function get_class_name() { return self::$class_name; diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 1832f8f873..7b0bec51f4 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -160,7 +160,18 @@ public function get_global_config_path( $create_config_file = false ) { // 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}"; - Process::create( Utils\esc_cmd( 'touch %s', $config_path ) )->run(); + + $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" ); + } } if ( is_readable( $config_path ) ) { @@ -200,12 +211,12 @@ static function ( $dir ) { } ); - $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; + if ( null === $project_config_path ) { + $this->project_config_path_debug = 'No project config found'; + return false; } + $this->project_config_path_debug = 'Using project config: ' . $project_config_path; return $project_config_path; } @@ -344,6 +355,14 @@ private static function guess_url( $assoc_args ) { 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 ) { return array_slice( $this->arguments, 0, count( $prefix ) ) === $prefix; } @@ -388,6 +407,16 @@ public function find_command_to_run( $args ) { $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]} "; + } + } + return sprintf( "'%s' is not a registered wp command. See 'wp help' for available commands.%s", $full_name, @@ -491,22 +520,17 @@ private function run_ssh_command( $connection_string ) { $pre_cmd = getenv( 'WP_CLI_SSH_PRE_CMD' ); if ( $pre_cmd ) { - $message = WP_CLI::warning( "WP_CLI_SSH_PRE_CMD found, executing the following command(s) on the remote machine:\n $pre_cmd" ); - - WP_CLI::log( $message ); + 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 = 'wp'; + $wp_binary = getenv( 'WP_CLI_SSH_BINARY' ) ?: 'wp'; $wp_args = array_slice( $GLOBALS['argv'], 1 ); if ( $this->alias && ! empty( $wp_args[0] ) && $this->alias === $wp_args[0] ) { @@ -569,50 +593,69 @@ private function generate_ssh_command( $bits, $wp_command ) { WP_CLI::debug( 'SSH ' . $bit . ': ' . $bits[ $bit ], 'bootstrap' ); } - $is_tty = function_exists( 'posix_isatty' ) && posix_isatty( STDOUT ); - $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'; + /* + * 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; + + if ( in_array( $bits['scheme'], [ 'docker', 'docker-compose', 'docker-compose-run' ], 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'; + } if ( 'docker' === $bits['scheme'] ) { - $command = 'docker exec %s%s%s sh -c %s'; + $command = 'docker exec %s%s%s%s%s sh -c %s'; $escaped_command = sprintf( $command, $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', - $is_tty ? '-t ' : '', + $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', + $is_stdout_tty && ! getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '-t ' : '', + $is_stdin_tty || getenv( 'WP_CLI_DOCKER_NO_INTERACTIVE' ) ? '' : '-i ', escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) ); } if ( 'docker-compose' === $bits['scheme'] ) { - $command = '%s exec %s%s%s sh -c %s'; + $command = '%s exec %s%s%s%s sh -c %s'; $escaped_command = sprintf( $command, $docker_compose_cmd, $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', - $is_tty ? '' : '-T ', + $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', + $is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) ); } if ( 'docker-compose-run' === $bits['scheme'] ) { - $command = '%s run %s%s%s %s'; + $command = '%s run %s%s%s%s%s %s'; $escaped_command = sprintf( $command, $docker_compose_cmd, $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', - $is_tty ? '' : '-T ', + $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', + $is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', + $is_stdin_tty || getenv( 'WP_CLI_DOCKER_NO_INTERACTIVE' ) ? '' : '-i ', escapeshellarg( $bits['host'] ), $wp_command ); } + // For "vagrant" & "ssh" schemes which don't provide a working-directory option, use `cd` + if ( $bits['path'] ) { + $wp_command = 'cd ' . escapeshellarg( $bits['path'] ) . '; ' . $wp_command; + } + // Vagrant ssh-config. if ( 'vagrant' === $bits['scheme'] ) { $cache = WP_CLI::get_cache(); @@ -630,10 +673,10 @@ private function generate_ssh_command( $bits, $wp_command ) { if ( empty( $bits['host'] ) || ( isset( $values['Host'] ) && $bits['host'] === $values['Host'] ) ) { $bits['scheme'] = 'ssh'; - $bits['host'] = $values['HostName']; - $bits['port'] = $values['Port']; - $bits['user'] = $values['User']; - $bits['key'] = $values['IdentityFile']; + $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'] : ''; } // If we could not resolve the bits still, fallback to just `vagrant ssh` @@ -669,7 +712,7 @@ private function generate_ssh_command( $bits, $wp_command ) { $bits['proxyjump'] ? sprintf( '-J %s', escapeshellarg( $bits['proxyjump'] ) ) : '', $bits['port'] ? sprintf( '-p %d', (int) $bits['port'] ) : '', $bits['key'] ? sprintf( '-i %s', escapeshellarg( $bits['key'] ) ) : '', - $is_tty ? '-t' : '-T', + $is_stdout_tty ? '-t' : '-T', WP_CLI::get_config( 'debug' ) ? '-vvv' : '-q', ]; @@ -709,7 +752,7 @@ public function get_wp_config_code( $wp_config_path = '' ) { $wp_config_path = Utils\locate_wp_config(); } - $wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) ); + $wp_config_code = file_get_contents( $wp_config_path ); // Detect and strip byte-order marks (BOMs). // This code assumes they can only be found on the first line. @@ -718,34 +761,24 @@ public function get_wp_config_code( $wp_config_path = '' ) { $length = strlen( $bom_sequence ); - while ( substr( $wp_config_code[0], 0, $length ) === $bom_sequence ) { + 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." ); - $wp_config_code[0] = substr( $wp_config_code[0], $length ); + $wp_config_code = substr( $wp_config_code, $length ); } } - $found_wp_settings = false; - - $lines_to_run = []; - - foreach ( $wp_config_code as $line ) { - if ( preg_match( '/^\s*require.+wp-settings\.php/', $line ) ) { - $found_wp_settings = true; - continue; - } + $count = 0; - $lines_to_run[] = $line; - } + $wp_config_code = preg_replace( '/\s*require(?:_once)?\s*.*wp-settings\.php.*\s*;/', '', $wp_config_code, -1, $count ); - if ( ! $found_wp_settings ) { + if ( 0 === $count ) { WP_CLI::error( 'Strange wp-config.php file: wp-settings.php is not loaded directly.' ); } - $source = implode( "\n", $lines_to_run ); - $source = Utils\replace_path_consts( $source, $wp_config_path ); + $source = Utils\replace_path_consts( $wp_config_code, $wp_config_path ); return preg_replace( '|^\s*\<\?php\s*|', '', $source ); } @@ -1070,39 +1103,6 @@ public function init_config() { $this->required_files['runtime'] = $this->config['require']; } - private function check_root() { - if ( $this->config['allow-root'] || getenv( 'WP_CLI_ALLOW_ROOT' ) ) { - return; # they're aware of the risks! - } - if ( count( $this->arguments ) >= 2 && 'cli' === $this->arguments[0] && in_array( $this->arguments[1], [ '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 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" - ); - } - private function run_alias_group( $aliases ) { Utils\check_proc_available( 'group alias' ); @@ -1150,7 +1150,6 @@ public function start() { 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." ); @@ -1621,9 +1620,7 @@ static function ( $current_site, $domain, $path ) { $explanation = 'Verify DOMAIN_CURRENT_SITE matches an existing site or use `--url=` to override.'; } } - if ( $explanation ) { - $message .= ' ' . $explanation; - } + $message .= ' ' . $explanation; WP_CLI::error( $message ); }, 10, @@ -1948,16 +1945,19 @@ private function auto_check_update() { * 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 $root_command Root command to start search for - * suggestions at. + * @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. * * @return string Suggestion that fits the user entry, or an empty string. */ - private function get_subcommand_suggestion( $entry, CompositeCommand $root_command = null ) { + private function get_subcommand_suggestion( $entry, $root_command = null ) { $commands = []; - $this->enumerate_commands( $root_command ?: WP_CLI::get_root_command(), $commands ); + if ( ( $root_command instanceof CompositeCommand ) === false ) { + $root_command = WP_CLI::get_root_command(); + } + $this->enumerate_commands( $root_command, $commands ); return Utils\get_suggestion( $entry, $commands, $threshold = 2 ); } diff --git a/php/WP_CLI/SynopsisParser.php b/php/WP_CLI/SynopsisParser.php index c4a0512d2b..d6fae4066d 100644 --- a/php/WP_CLI/SynopsisParser.php +++ b/php/WP_CLI/SynopsisParser.php @@ -101,12 +101,7 @@ public static function render( &$synopsis ) { $value .= "{$rendered_arg} "; } } - $rendered = ''; - foreach ( $bits as $v ) { - if ( ! empty( $v ) ) { - $rendered .= $v; - } - } + $rendered = implode( '', $bits ); $synopsis = array_merge( $reordered_synopsis['positional'], diff --git a/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php b/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php index 2e40c5a67c..e2c16efa58 100644 --- a/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php +++ b/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php @@ -128,7 +128,7 @@ public function delete_by_key( $key ) { * * @throws NonExistentKeyException * - * @return static + * @return self */ public function traverse_to( array $key_path ) { $current = array_shift( $key_path ); @@ -139,13 +139,13 @@ public function traverse_to( array $key_path ) { if ( ! $this->exists( $current ) ) { $exception = new NonExistentKeyException( "No data exists for key \"{$current}\"" ); - $exception->set_traverser( new static( $this->data, $current, $this->parent ) ); + $exception->set_traverser( new self( $this->data, $current, $this->parent ) ); throw $exception; } foreach ( $this->data as $key => &$key_data ) { if ( $key === $current ) { - $traverser = new static( $key_data, $key, $this ); + $traverser = new self( $key_data, $key, $this ); return $traverser->traverse_to( $key_path ); } } diff --git a/php/WP_CLI/WpOrgApi.php b/php/WP_CLI/WpOrgApi.php index 29c47728ec..17eefd1489 100644 --- a/php/WP_CLI/WpOrgApi.php +++ b/php/WP_CLI/WpOrgApi.php @@ -278,7 +278,7 @@ public function get_theme_info( $theme, $locale = 'en_US', array $fields = [] ) /** * Gets a set of salts in the format required by `wp-config.php`. * - * @return bool|string False on failure. A string of PHP define() statements on success. + * @return string A string of PHP define() statements. * @throws RuntimeException If the remote request fails. */ public function get_salts() { @@ -323,7 +323,7 @@ private function json_get_request( $url, $headers = [], $options = [] ) { * @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|false False on failure. Response body string on success. + * @return string Response body. * @throws RuntimeException If the remote request fails. */ private function get_request( $url, $headers = [], $options = [] ) { diff --git a/php/bootstrap.php b/php/bootstrap.php index f0957bcfb4..e2c27c38a7 100644 --- a/php/bootstrap.php +++ b/php/bootstrap.php @@ -22,9 +22,10 @@ function get_bootstrap_steps() { Bootstrap\DeclareAbstractBaseCommand::class, Bootstrap\IncludeFrameworkAutoloader::class, Bootstrap\ConfigureRunner::class, - Bootstrap\IncludeRequestsAutoloader::class, Bootstrap\InitializeColorization::class, Bootstrap\InitializeLogger::class, + Bootstrap\CheckRoot::class, + Bootstrap\IncludeRequestsAutoloader::class, Bootstrap\DefineProtectedCommands::class, Bootstrap\LoadExecCommand::class, Bootstrap\LoadRequiredCommand::class, diff --git a/php/class-wp-cli.php b/php/class-wp-cli.php index 062c8c1845..f3b04b368c 100644 --- a/php/class-wp-cli.php +++ b/php/class-wp-cli.php @@ -262,7 +262,7 @@ public static function colorize( $string ) { * * @param string $when Identifier for the hook. * @param mixed $callback Callback to execute when hook is called. - * @return null + * @return void */ public static function add_hook( $when, $callback ) { if ( array_key_exists( $when, self::$hooks_passed ) ) { @@ -591,6 +591,8 @@ public static function add_command( $name, $callable, $args = [] ) { ); } + /** @var Dispatcher\Subcommand $leaf_command */ + if ( isset( $args['shortdesc'] ) ) { $leaf_command->set_shortdesc( $args['shortdesc'] ); } @@ -738,7 +740,7 @@ public static function remove_deferred_addition( $name ) { * @category Output * * @param string $message Message to display to the end user. - * @return null + * @return void */ public static function line( $message = '' ) { echo $message . "\n"; @@ -770,7 +772,7 @@ public static function log( $message ) { /** * Display success message prefixed with "Success: ". * - * Success message is written to STDOUT. + * Success message is written to STDOUT, or discarded when `--quiet` flag is supplied. * * Typically recommended to inform user of successful script conclusion. * @@ -788,7 +790,7 @@ public static function log( $message ) { * @category Output * * @param string $message Message to write to STDOUT. - * @return null + * @return void */ public static function success( $message ) { if ( null === self::$logger ) { @@ -826,7 +828,7 @@ public static function success( $message ) { * @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 null + * @return void */ public static function debug( $message, $group = false ) { static $storage = []; @@ -836,7 +838,7 @@ public static function debug( $message, $group = false ) { return; } - if ( ! empty( $storage ) && self::$logger ) { + if ( ! empty( $storage ) ) { foreach ( $storage as $entry ) { list( $stored_message, $stored_group ) = $entry; self::$logger->debug( self::error_to_string( $stored_message ), $stored_group ); @@ -850,7 +852,7 @@ public static function debug( $message, $group = false ) { /** * Display warning message prefixed with "Warning: ". * - * Warning message is written to STDERR. + * Warning message is written to STDERR, or discarded when `--quiet` flag is supplied. * * Use instead of `WP_CLI::debug()` when script execution should be permitted * to continue. @@ -869,7 +871,7 @@ public static function debug( $message, $group = false ) { * @category Output * * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR. - * @return null + * @return void */ public static function warning( $message ) { if ( null === self::$logger ) { @@ -1289,7 +1291,15 @@ 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. + * @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. + * @param array $command_args Contains additional command line arguments for the command. Each element represents a single argument. Default: empty array. + * } * @return mixed */ public static function runcommand( $command, $options = [] ) { @@ -1349,6 +1359,9 @@ public static function runcommand( $command, $options = [] ) { $pipes = []; $proc = Utils\proc_open_compat( $runcommand, $descriptors, $pipes, getcwd() ); + $stdout = ''; + $stderr = ''; + if ( $return ) { $stdout = stream_get_contents( $pipes[1] ); fclose( $pipes[1] ); diff --git a/php/commands/src/CLI_Command.php b/php/commands/src/CLI_Command.php index fef2ea54f9..614fcb5644 100644 --- a/php/commands/src/CLI_Command.php +++ b/php/commands/src/CLI_Command.php @@ -199,7 +199,7 @@ public function info( $_, $assoc_args ) { * : 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. + * : Limit the output to specific object fields. Defaults to version,update_type,package_url,status,requires_php. * * [--format=] * : Render output in a particular format. @@ -235,7 +235,7 @@ public function check_update( $_, $assoc_args ) { if ( $updates ) { $formatter = new Formatter( $assoc_args, - [ 'version', 'update_type', 'package_url' ] + [ 'version', 'update_type', 'package_url', 'status', 'requires_php' ] ); $formatter->display_items( $updates ); } elseif ( empty( $assoc_args['format'] ) || 'table' === $assoc_args['format'] ) { @@ -286,7 +286,7 @@ public function check_update( $_, $assoc_args ) { * * # Update CLI. * $ wp cli update - * You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y + * 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. @@ -305,29 +305,37 @@ public function update( $_, $assoc_args ) { } if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) { - WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to the latest nightly?', WP_CLI_VERSION ), $assoc_args ); + 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 ); $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'; } elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) { - WP_CLI::confirm( sprintf( 'You have version %s. Would you like to update to the latest stable release?', WP_CLI_VERSION ), $assoc_args ); + 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 ); $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'; } else { $updates = $this->get_updates( $assoc_args ); - if ( empty( $updates ) ) { + $newest = $this->array_find( + $updates, + static function ( $update ) { + return 'available' === $update['status']; + } + ); + + if ( ! $newest ) { $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 ); } WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) ); @@ -344,17 +352,8 @@ public function update( $_, $assoc_args ) { Utils\http_request( 'GET', $download_url, null, $headers, $options ); unset( $options['filename'] ); - $md5_response = Utils\http_request( 'GET', $md5_url, null, $headers, $options ); - 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})." ); - } + + $this->validate_hashes( $temp, $sha512_url, $md5_url ); $allow_root = WP_CLI::get_runner()->config['allow-root'] ? '--allow-root' : ''; $php_binary = Utils\get_php_binary(); @@ -390,6 +389,41 @@ class_exists( '\cli\Colors' ); // This autoloads \cli\Colors - after we move the WP_CLI::success( sprintf( 'Updated WP-CLI to %s.', $updated_version ) ); } + /** + * @param string $file Release file path. + * @param string $sha512_url URL to sha512 hash. + * @param string $md5_url URL to md5 hash. + * + * @return void + * @throws \WP_CLI\ExitException + */ + private function validate_hashes( $file, $sha512_url, $md5_url ) { + $algos = [ + 'sha512' => $sha512_url, + 'md5' => $md5_url, + ]; + + foreach ( $algos as $algo => $url ) { + $response = Utils\http_request( 'GET', $url ); + if ( '20' !== substr( $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. */ @@ -423,6 +457,9 @@ private function get_updates( $assoc_args ) { 'minor' => false, 'patch' => false, ]; + + $updates_unavailable = []; + foreach ( $release_data as $release ) { // Get rid of leading "v" if there is one set. @@ -432,23 +469,63 @@ private function get_updates( $assoc_args ) { } $update_type = Utils\get_named_sem_ver( $release_version, WP_CLI_VERSION ); + if ( ! $update_type ) { continue; } - if ( ! isset( $release->assets[0]->browser_download_url ) ) { + // Release is older than one we already have on file. + if ( ! empty( $updates[ $update_type ] ) && ! Comparator::greaterThan( $release_version, $updates[ $update_type ]['version'] ) ) { continue; } - if ( ! empty( $updates[ $update_type ] ) && ! Comparator::greaterThan( $release_version, $updates[ $update_type ]['version'] ) ) { + $package_url = null; + $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 ) { + $manifest_data = json_decode( $response->body ); + } + } + } + + if ( ! $package_url ) { continue; } - $updates[ $update_type ] = [ - 'version' => $release_version, - 'update_type' => $update_type, - 'package_url' => $release->assets[0]->browser_download_url, - ]; + // 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 : '', + ]; + } } foreach ( $updates as $type => $value ) { @@ -470,16 +547,67 @@ private function get_updates( $assoc_args ) { 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 ) { - $updates['nightly'] = [ - 'version' => $nightly_version, - 'update_type' => 'nightly', - 'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar', - ]; + $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 ) { + $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' => 'unvailable', + '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; } } - return array_values( $updates ); + return null; } /** diff --git a/php/compat.php b/php/compat.php index 306e5a9a7d..b3d9bbc7f3 100644 --- a/php/compat.php +++ b/php/compat.php @@ -1,118 +1 @@ =' ) ) { require $path; } } -function get_upgrader( $class, $insecure = false ) { +/** + * + * @param class-string $class_name + * @param bool $insecure + * + * @return \WP_Upgrader Upgrader instance. + * @throws \ReflectionException + */ +function get_upgrader( $class_name, $insecure = false ) { 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'; @@ -167,30 +200,31 @@ function get_upgrader( $class, $insecure = false ) { $uses_insecure_flag = false; - $reflection = new ReflectionClass( $class ); - if ( $reflection ) { - $constructor = $reflection->getConstructor(); - if ( $constructor ) { - $arguments = $constructor->getParameters(); - /** @var ReflectionParameter $argument */ - foreach ( $arguments as $argument ) { - if ( 'insecure' === $argument->name ) { - $uses_insecure_flag = true; - break; - } + $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 ) { - return new $class( new UpgraderSkin(), $insecure ); + return new $class_name( new UpgraderSkin(), $insecure ); } else { - return new $class( new UpgraderSkin() ); + return new $class_name( new UpgraderSkin() ); } } /** * Converts a plugin basename back into a friendly slug. + * + * @param string $basename + * @return string */ function get_plugin_name( $basename ) { if ( false === strpos( $basename, '/' ) ) { @@ -202,6 +236,12 @@ 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 ) ); @@ -217,10 +257,22 @@ function is_plugin_skipped( $file ) { return in_array( $name, array_filter( $skipped_plugins ), true ); } +/** + * Get theme name from path. + * + * @param string $path + * @return string + */ function get_theme_name( $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 ); @@ -239,6 +291,8 @@ function is_theme_skipped( $path ) { /** * Register the sidebar for unused widgets. * Core does this in /wp-admin/widgets.php, which isn't helpful. + * + * @return void */ function wp_register_unused_sidebar() { @@ -268,6 +322,8 @@ 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 ) { @@ -315,18 +371,16 @@ function wp_get_cache_type() { $message = 'WP LCache'; } elseif ( function_exists( 'w3_instance' ) ) { - $config = w3_instance( 'W3_Config' ); - $message = 'Unknown'; + $config = w3_instance( 'W3_Config' ); if ( $config->get_boolean( 'objectcache.enabled' ) ) { $message = 'W3TC ' . $config->get_string( 'objectcache.engine' ); } - } else { - $message = 'Unknown'; } } else { $message = 'Default'; } + return $message; } @@ -340,6 +394,8 @@ 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; @@ -378,15 +434,13 @@ 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 */ function wp_get_table_names( $args, $assoc_args = [] ) { global $wpdb; - $tables = []; - // 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.' ); diff --git a/php/utils.php b/php/utils.php index 6b448f8cab..8468d208e0 100644 --- a/php/utils.php +++ b/php/utils.php @@ -51,6 +51,7 @@ * 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. */ function inside_phar( $path = null ) { if ( null === $path ) { @@ -95,6 +96,11 @@ function () use ( $tmp_path ) { return $tmp_path; } +/** + * Load dependencies. + * + * @return void|never + */ function load_dependencies() { if ( inside_phar() ) { if ( file_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) { @@ -121,6 +127,11 @@ 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). @@ -136,11 +147,25 @@ function get_vendor_paths() { return $vendor_paths; } -// Using require() directly inside a class grants access to private methods to the loaded code. +/** + * 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 + */ 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"; @@ -165,7 +190,7 @@ function load_command( $name ) { * } * * @param array|object $it Either a plain array or another iterator. - * @param callback $fn The function to apply to an element. + * @param callable $fn The function to apply to an element. * @return object An iterator that applies the given callback(s). */ function iterator_map( $it, $fn ) { @@ -187,9 +212,9 @@ function iterator_map( $it, $fn ) { /** * 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. + * @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. */ function find_file_upward( $files, $dir = null, $stop_check = null ) { @@ -219,6 +244,11 @@ function find_file_upward( $files, $dir = null, $stop_check = null ) { return null; } +/** + * Determine whether a path is absolute. + * @param string $path + * @return bool + */ function is_path_absolute( $path ) { // Windows. if ( isset( $path[1] ) && ':' === $path[1] ) { @@ -231,7 +261,7 @@ function is_path_absolute( $path ) { /** * Composes positional arguments into a command string. * - * @param array $args Positional arguments to compose. + * @param array $args Positional arguments to compose. * @return string */ function args_to_str( $args ) { @@ -241,7 +271,7 @@ function args_to_str( $args ) { /** * Composes associative arguments into a command string. * - * @param array $assoc_args Associative arguments to compose. + * @param array $assoc_args Associative arguments to compose. * @return string */ function assoc_args_to_str( $assoc_args ) { @@ -269,6 +299,8 @@ function assoc_args_to_str( $assoc_args ) { /** * Given a template string and an arbitrary number of arguments, * returns the final command, with the parameters escaped. + * + * @param array $cmd */ function esc_cmd( $cmd ) { if ( func_num_args() < 2 ) { @@ -309,6 +341,13 @@ function locate_wp_config() { return $path; } +/** + * Compare a WordPress version. + * + * @param string $since + * @param string $operator + * @return bool + */ function wp_version_compare( $since, $operator ) { $wp_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); $since = str_replace( '-src', '', $since ); @@ -356,8 +395,8 @@ function wp_version_compare( $since, $operator ) { * @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 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. */ function format_items( $format, $items, $fields ) { $assoc_args = [ @@ -373,13 +412,14 @@ function format_items( $format, $items, $fields ) { * * @access public * - * @param resource $fd File descriptor. - * @param array $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 = [] ) { if ( ! empty( $headers ) ) { - fputcsv( $fd, $headers ); + $headers = array_map( __NAMESPACE__ . '\escape_csv_value', $headers ); + fputcsv( $fd, $headers, ',', '"', '\\' ); } foreach ( $rows as $row ) { @@ -387,16 +427,17 @@ function write_csv( $fd, $rows, $headers = [] ) { $row = pick_fields( $row, $headers ); } - fputcsv( $fd, array_values( $row ) ); + $row = array_map( __NAMESPACE__ . '\escape_csv_value', $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 $item Associative array or object to pick fields from. + * @param array $fields List of fields to pick. + * @return array */ function pick_fields( $item, $fields ) { $values = []; @@ -434,7 +475,7 @@ function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) { do { $tmpfile = basename( $title ); $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); - $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- no crypto and WP not loaded. + $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' ); if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { @@ -478,7 +519,7 @@ 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 + * @return array */ function mysql_host_to_cli_args( $raw_host ) { $assoc_args = []; @@ -513,14 +554,14 @@ function mysql_host_to_cli_args( $raw_host ) { * * @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. + * @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. @@ -599,6 +640,9 @@ function run_mysql_command( $cmd, $assoc_args, $_ = null, $send_to_shell = true, * 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 = [] ) { if ( ! file_exists( $template_name ) ) { @@ -788,6 +832,8 @@ function http_request( $method, $url, $data = null, $headers = [], $options = [] $options['verify'] = ! empty( ini_get( 'curl.cainfo' ) ) ? ini_get( 'curl.cainfo' ) : true; } + $options = WP_CLI::do_hook( 'http_request_options', $options ); + RequestsLibrary::register_autoloader(); $request_method = [ RequestsLibrary::get_class_name(), 'request' ]; @@ -987,9 +1033,9 @@ function get_named_sem_ver( $new_version, $original_version ) { * @access public * @category Input * - * @param array $assoc_args Arguments array. - * @param string $flag Flag to get the value. - * @param mixed $default Default value for the flag. Default: 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 ) { @@ -1102,6 +1148,8 @@ function get_temp_dir() { * * @access public * + * @param string $url + * @param int $component * @return mixed */ function parse_ssh_url( $url, $component = -1 ) { @@ -1155,6 +1203,7 @@ function parse_ssh_url( $url, $component = -1 ) { * @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'; @@ -1185,11 +1234,11 @@ 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})*|(?:[^\1]+?[^\\\\](\\{2})*))\1|--[^\s=]+=[^\s]+|--[^\s=]+|(["\'])((\\{2})*|(?:[^\5]+?[^\\\\](\\{2})*))\5|[^\s]+)/', $arguments, $matches ); - $argv = isset( $matches[0] ) ? $matches[0] : []; + $argv = $matches[0]; return array_map( static function ( $arg ) { foreach ( [ '"', "'" ] as $char ) { @@ -1198,7 +1247,7 @@ static function ( $arg ) { break; } } - return $arg; + return $arg; }, $argv ); @@ -1258,9 +1307,9 @@ function isPiped() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionNa * * Has no effect on paths which do not use glob patterns. * - * @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. + * @param string|array $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. */ function expand_globs( $paths, $flags = 'default' ) { // Compatibility for systems without GLOB_BRACE. @@ -1299,7 +1348,7 @@ function expand_globs( $paths, $flags = 'default' ) { * * @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. @@ -1364,8 +1413,8 @@ function glob_brace( $pattern, $dummy_flags = null ) { // phpcs:ignore Generic.C // 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 ) ) { @@ -1393,9 +1442,9 @@ 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 ) { @@ -1574,12 +1623,12 @@ function get_php_binary() { * * @access public * - * @param string $cmd 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 string $cmd 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). * @return resource Command stripped of any environment variable settings. */ function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = null, $other_options = null ) { @@ -1595,8 +1644,8 @@ function proc_open_compat( $cmd, $descriptorspec, &$pipes, $cwd = null, $env = n * * @access private * - * @param string $cmd Command to execute. - * @param array &$env Array of existing environment variables. Will be modified if any settings in command. + * @param string $cmd 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 ) { @@ -1627,6 +1676,15 @@ function _proc_open_compat_win_env( $cmd, &$env ) { * or real_escape next. */ function esc_like( $text ) { + global $wpdb; + + // Check if the esc_like() method exists on the global $wpdb object. + // We need to do this because to ensure compatibilty layers like the + // SQLite integration plugin still work. + if ( null !== $wpdb && method_exists( $wpdb, 'esc_like' ) ) { + return $wpdb->esc_like( $text ); + } + return addcslashes( $text, '_%\\' ); } @@ -1634,8 +1692,8 @@ function esc_like( $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. + * @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. */ function esc_sql_ident( $idents ) { $backtick = static function ( $v ) { @@ -1673,10 +1731,10 @@ 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 ) ) { @@ -1730,7 +1788,7 @@ function describe_callable( $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. + * @param array $pair The class and method pair to check. * @return bool */ function is_valid_class_and_method_pair( $pair ) { @@ -1771,37 +1829,99 @@ function pluralize( $noun, $count = null ) { } /** - * Get the path to the mysql binary. + * Return the detected database type. + * + * Can be either 'sqlite' (if in a WordPress installation with the SQLite drop-in), + * 'mysql', or 'mariadb'. * - * @return string Path to the mysql binary, or an empty string if not found. + * @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 ) { - $result = Process::create( '/usr/bin/env which mysql', null, null )->run(); + if ( null !== $path ) { + return $path; + } - if ( 0 !== $result->return_code ) { - $path = ''; - } else { - $path = trim( $result->stdout ); + $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 database. + * Get the version of the MySQL or MariaDB database. * - * @return string Version of the MySQL database, or an empty string if not - * found. + * @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 ) { - $result = Process::create( '/usr/bin/env mysql --version', null, null )->run(); + if ( null !== $version ) { + return $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 = ''; @@ -1813,6 +1933,24 @@ function get_mysql_version() { return $version; } +/** + * Returns the correct `dump` command based on the detected database type. + * + * @return string The appropriate dump command. + */ +function get_sql_dump_command() { + return 'mariadb' === get_db_type() ? 'mariadb-dump' : 'mysqldump'; +} + +/** + * Returns the correct `check` command based on the detected database type. + * + * @return string The appropriate check command. + */ +function get_sql_check_command() { + return 'mariadb' === get_db_type() ? 'mariadb-check' : 'mysqlcheck'; +} + /** * Get the SQL modes of the MySQL session. * @@ -1822,8 +1960,16 @@ function get_mysql_version() { function get_sql_modes() { static $sql_modes = null; - if ( null === $sql_modes ) { - $result = Process::create( '/usr/bin/env mysql --no-auto-rehash --batch --skip-column-names --execute="SELECT @@SESSION.sql_mode"', null, null )->run(); + 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 = []; @@ -1888,3 +2034,33 @@ function get_hook_description( $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; +} diff --git a/php/wp-cli.php b/php/wp-cli.php index 153e44b116..13a910af54 100644 --- a/php/wp-cli.php +++ b/php/wp-cli.php @@ -19,14 +19,17 @@ // Set common headers, to prevent warnings from plugins. $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0'; -$_SERVER['HTTP_USER_AGENT'] = ''; +$_SERVER['HTTP_USER_AGENT'] = ( ! empty( getenv( 'WP_CLI_USER_AGENT' ) ) ? getenv( 'WP_CLI_USER_AGENT' ) : 'WP CLI ' . WP_CLI_VERSION ); $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; require_once WP_CLI_ROOT . '/php/bootstrap.php'; if ( getenv( 'WP_CLI_EARLY_REQUIRE' ) ) { - require_once getenv( 'WP_CLI_EARLY_REQUIRE' ); + foreach ( explode( ',', getenv( 'WP_CLI_EARLY_REQUIRE' ) ) as $wp_cli_early_require ) { + require_once trim( $wp_cli_early_require ); + } + unset( $wp_cli_early_require ); } WP_CLI\bootstrap(); diff --git a/php/wp-settings-cli.php b/php/wp-settings-cli.php index 8c3066140a..f23f2c69b1 100644 --- a/php/wp-settings-cli.php +++ b/php/wp-settings-cli.php @@ -458,7 +458,7 @@ function get_magic_quotes_gpc() { * AJAX requests should use wp-admin/admin-ajax.php. admin-ajax.php can handle requests for * users not logged in. * - * @link https://codex.wordpress.org/AJAX_in_Plugins + * @link https://developer.wordpress.org/plugins/javascript/ajax/ * * @since 3.0.0 */ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1ef1aba80f..316b7b1395 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,15 +6,21 @@ beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="true" beStrictAboutTodoAnnotatedTests="true" + convertErrorsToExceptions="true" + convertWarningsToExceptions="true" + convertNoticesToExceptions="true" + convertDeprecationsToExceptions="true" colors="true" verbose="true"> - - tests - + + + tests + + - - - src - - + + + php + + diff --git a/tests/CommandFactoryTest.php b/tests/CommandFactoryTest.php index afc95a5509..cdf33fdf0b 100644 --- a/tests/CommandFactoryTest.php +++ b/tests/CommandFactoryTest.php @@ -2,10 +2,12 @@ use WP_CLI\Tests\TestCase; -require_once dirname( __DIR__ ) . '/php/class-wp-cli-command.php'; - class CommandFactoryTest extends TestCase { + public static function set_up_before_class() { + require_once dirname( __DIR__ ) . '/php/class-wp-cli-command.php'; + } + /** * @dataProvider dataProviderExtractLastDocComment */ diff --git a/tests/FileCacheTest.php b/tests/FileCacheTest.php index 301397fe0c..2e8fce5a1b 100644 --- a/tests/FileCacheTest.php +++ b/tests/FileCacheTest.php @@ -5,10 +5,12 @@ use WP_CLI\Tests\TestCase; use WP_CLI\Utils; -require_once dirname( __DIR__ ) . '/php/class-wp-cli.php'; - class FileCacheTest extends TestCase { + public static function set_up_before_class() { + require_once dirname( __DIR__ ) . '/php/class-wp-cli.php'; + } + /** * Test get_root() deals with backslashed directory. */ diff --git a/tests/HelpTest.php b/tests/HelpTest.php index accaad7073..6be0c6eb2f 100644 --- a/tests/HelpTest.php +++ b/tests/HelpTest.php @@ -2,12 +2,14 @@ use WP_CLI\Tests\TestCase; -require_once dirname( __DIR__ ) . '/php/class-wp-cli.php'; -require_once dirname( __DIR__ ) . '/php/class-wp-cli-command.php'; -require_once dirname( __DIR__ ) . '/php/commands/help.php'; - class HelpTest extends TestCase { + public static function set_up_before_class() { + require_once dirname( __DIR__ ) . '/php/class-wp-cli.php'; + require_once dirname( __DIR__ ) . '/php/class-wp-cli-command.php'; + require_once dirname( __DIR__ ) . '/php/commands/help.php'; + } + public function test_parse_reference_links() { $test_class = new ReflectionClass( 'Help_Command' ); $method = $test_class->getMethod( 'parse_reference_links' ); diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index f220cb4301..77fab558b8 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -5,11 +5,13 @@ use WP_CLI\Tests\TestCase; use WP_CLI\Utils; -require_once dirname( __DIR__ ) . '/php/class-wp-cli.php'; -require_once __DIR__ . '/mock-requests-transport.php'; - class UtilsTest extends TestCase { + public static function set_up_before_class() { + require_once dirname( __DIR__ ) . '/php/class-wp-cli.php'; + require_once __DIR__ . '/mock-requests-transport.php'; + } + public function testIncrementVersion() { // Keyword increments. $this->assertEquals( @@ -454,7 +456,7 @@ public function testHttpRequestBadAddress() { $prev_logger = WP_CLI::get_logger(); // Enable exit exception. - $class_wp_cli_capture_exit->setValue( true ); + $class_wp_cli_capture_exit->setValue( null, true ); $logger = new Loggers\Execution(); WP_CLI::set_logger( $logger ); @@ -472,7 +474,7 @@ public function testHttpRequestBadAddress() { $this->assertTrue( 0 === strpos( $logger->stderr, 'Error: Failed to get url' ) ); // Restore. - $class_wp_cli_capture_exit->setValue( $prev_capture_exit ); + $class_wp_cli_capture_exit->setValue( null, $prev_capture_exit ); WP_CLI::set_logger( $prev_logger ); } @@ -481,12 +483,12 @@ public static function dataHttpRequestBadCAcert() { 'default request' => [ [], RuntimeException::class, - 'Failed to get url \'https://example.com\': cURL error 77: error setting certificate verify locations:', + 'Failed to get url \'https://example.com\': cURL error 77: error setting certificate', ], 'secure request' => [ [ 'insecure' => false ], RuntimeException::class, - 'Failed to get url \'https://example.com\': cURL error 77: error setting certificate verify locations:', + 'Failed to get url \'https://example.com\': cURL error 77: error setting certificate', ], 'insecure request' => [ [ 'insecure' => true ], @@ -675,7 +677,7 @@ public function testReportBatchOperationResults( $stdout, $stderr, $noun, $verb, $prev_logger = WP_CLI::get_logger(); // Enable exit exception. - $class_wp_cli_capture_exit->setValue( true ); + $class_wp_cli_capture_exit->setValue( null, true ); $logger = new Loggers\Execution(); WP_CLI::set_logger( $logger ); @@ -691,7 +693,7 @@ public function testReportBatchOperationResults( $stdout, $stderr, $noun, $verb, $this->assertSame( $stderr, $logger->stderr ); // Restore. - $class_wp_cli_capture_exit->setValue( $prev_capture_exit ); + $class_wp_cli_capture_exit->setValue( null, $prev_capture_exit ); WP_CLI::set_logger( $prev_logger ); } @@ -775,28 +777,52 @@ public static function dataProcOpenCompatWinEnv() { ]; } + public static function dataEscLike() { + return [ + [ 'howdy%', 'howdy\\%' ], + [ 'howdy_', 'howdy\\_' ], + [ 'howdy\\', 'howdy\\\\' ], + [ 'howdy\\howdy%howdy_', 'howdy\\\\howdy\\%howdy\\_' ], + [ 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?', 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?' ], + ]; + } + /** - * Copied from core "tests/phpunit/tests/db.php" (adapted to not use `$wpdb`). + * @dataProvider dataEscLike */ - public function test_esc_like() { - $inputs = [ - 'howdy%', // Single Percent. - 'howdy_', // Single Underscore. - 'howdy\\', // Single slash. - 'howdy\\howdy%howdy_', // The works. - 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?', // Plain text. - ]; - $expected = [ - 'howdy\\%', - 'howdy\\_', - 'howdy\\\\', - 'howdy\\\\howdy\\%howdy\\_', - 'howdy\'"[[]*#[^howdy]!+)(*&$#@!~|}{=--`/.,<>?', - ]; + public function test_esc_like( $input, $expected ) { + $this->assertEquals( $expected, Utils\esc_like( $input ) ); + } - foreach ( $inputs as $key => $input ) { - $this->assertEquals( $expected[ $key ], Utils\esc_like( $input ) ); + /** + * @dataProvider dataEscLike + */ + public function test_esc_like_with_wpdb( $input, $expected ) { + global $wpdb; + $wpdb = $this->getMockBuilder( 'stdClass' ); + + // Handle different PHPUnit versions (5.7 for PHP 5.6 vs newer versions) + // This can be simplified if we drop support for PHP 5.6. + if ( method_exists( $wpdb, 'addMethods' ) ) { + $wpdb = $wpdb->addMethods( [ 'esc_like' ] ); + } else { + $wpdb = $wpdb->setMethods( [ 'esc_like' ] ); } + + $wpdb = $wpdb->getMock(); + $wpdb->method( 'esc_like' ) + ->willReturn( addcslashes( $input, '_%\\' ) ); + $this->assertEquals( $expected, Utils\esc_like( $input ) ); + $this->assertEquals( $expected, Utils\esc_like( $input ) ); + } + + /** + * @dataProvider dataEscLike + */ + public function test_esc_like_with_wpdb_being_null( $input, $expected ) { + global $wpdb; + $wpdb = null; + $this->assertEquals( $expected, Utils\esc_like( $input ) ); } /** @@ -888,6 +914,155 @@ public static function dataParseUrl() { ]; } + /** + * @dataProvider dataEscapeCsvValue + */ + public function testEscapeCsvValue( $input, $expected ) { + $this->assertEquals( $expected, Utils\escape_csv_value( $input ) ); + } + + public static function dataEscapeCsvValue() { + 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() { + // 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 ); + + // 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() { + // 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 ); + + // 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() { + // 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 ); + + // 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() { $expected = "define( 'ABSPATH', dirname( 'C:\\\\Users\\\\test\'s\\\\site' ) . '/' );"; $source = "define( 'ABSPATH', dirname( __FILE__ ) . '/' );"; diff --git a/tests/WP_CLI/Iterators/CSVTest.php b/tests/WP_CLI/Iterators/CSVTest.php index 5d5b967a80..8d403bde9a 100644 --- a/tests/WP_CLI/Iterators/CSVTest.php +++ b/tests/WP_CLI/Iterators/CSVTest.php @@ -93,7 +93,7 @@ private function create_csv_file( $data, $delimiter = ',' ) { $fp = fopen( $filename, 'wb' ); foreach ( $data as $row ) { - fputcsv( $fp, $row, $delimiter ); + fputcsv( $fp, $row, $delimiter, '"', '\\' ); } fclose( $fp ); diff --git a/tests/WP_CLI/WpOrgApiTest.php b/tests/WP_CLI/WpOrgApiTest.php index efea426318..f6f3a5daa4 100644 --- a/tests/WP_CLI/WpOrgApiTest.php +++ b/tests/WP_CLI/WpOrgApiTest.php @@ -3,10 +3,12 @@ use WP_CLI\Tests\TestCase; use WP_CLI\WpOrgApi; -require_once dirname( __DIR__ ) . '/mock-requests-transport.php'; - class WpOrgApiTest extends TestCase { + public static function set_up_before_class() { + require_once dirname( __DIR__ ) . '/mock-requests-transport.php'; + } + public static function data_http_request_verify() { return [ 'can retrieve core checksums' => [ diff --git a/utils/install-requests.sh b/utils/install-requests.sh index 49b0133887..c5d5957854 100755 --- a/utils/install-requests.sh +++ b/utils/install-requests.sh @@ -1,6 +1,6 @@ #!/bin/bash -REQUESTS_TAG="v2.0.7" +REQUESTS_TAG="v2.0.12" DOWNLOAD_LINK="https://github.com/WordPress/Requests/archive/refs/tags/${REQUESTS_TAG}.tar.gz"