diff --git a/.distignore b/.distignore deleted file mode 100644 index 3da91f6..0000000 --- a/.distignore +++ /dev/null @@ -1,24 +0,0 @@ -# Directories -.git -.github -.wordpress-org -bin -features -node_modules -tests -vendor - -# Files -.distignore -.gitignore -behat.yml -composer.json -composer.lock -CONTRIBUTING.md -Gruntfile.js -package-lock.json -package.json -phpcs.xml.dist -phpstan.neon.dist -phpunit.xml.dist -SECURITY.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5d36dbe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a5e4222 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,26 @@ +* text=auto + +# Directories +/.git export-ignore +/.github export-ignore +/.wordpress-org export-ignore +/node_modules export-ignore +/tests export-ignore +/vendor export-ignore + +# Files +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.npmrc export-ignore +/.nvmrc export-ignore +/CODE_OF_CONDUCT.md export-ignore +/codeception.dist.yml export-ignore +/composer.lock export-ignore +/CONTRIBUTING.md export-ignore +/docker-compose.yml export-ignore +/package-lock.json export-ignore +/package.json export-ignore +/phpcs.xml.dist export-ignore +/phpstan.neon.dist export-ignore +/SECURITY.md export-ignore diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml new file mode 100644 index 0000000..89082fd --- /dev/null +++ b/.github/workflows/acceptance-tests.yml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + +name: Acceptance Tests +on: + push: + branches: + - 'develop' + - 'trunk' + - 'master' + pull_request: + branches: + - '**' + schedule: + # Once weekly on Mondays at 04:00 UTC. + # + # ┌───────────── minute (0 - 59) + # │ ┌────────── hour (0 - 23) + # │ │ ┌─────── day of the month (1 - 31) + # │ │ │ ┌──── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌─ day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ │ + - cron: '0 4 * * 1' + workflow_dispatch: + +jobs: + test: + name: WP ${{ matrix.wp }} / PHP ${{ matrix.php }} + strategy: + matrix: + wp: + - 'nightly' + - '6.4' + - '6.3' + php: + - '8.2' + - '8.1' + - '8.0' + - '7.4' + dev: + - ${{ github.ref_name == 'develop' }} + exclude: + # Only run the nightly tests on the develop branch. + - wp: 'nightly' + dev: false + fail-fast: false + uses: johnbillion/plugin-infrastructure/.github/workflows/reusable-acceptance-tests.yml@trunk + with: + wp: ${{ matrix.wp }} + php: ${{ matrix.php }} + node: false diff --git a/.github/workflows/changelog.js b/.github/workflows/changelog.js deleted file mode 100644 index 92ff148..0000000 --- a/.github/workflows/changelog.js +++ /dev/null @@ -1,46 +0,0 @@ -const github = require('@actions/github'); -const semver = require('semver'); -const replace = require('replace-in-file'); - -const filename = process.argv[2] || 'readme.md'; -const myToken = process.env.TOKEN; - -async function run() { - const api = new github.GitHub(myToken); - - const { data: releases } = await api.repos.listReleases( github.context.repo ); - - let published = releases.filter( release => - ! release.draft && ! release.prerelease - ); - - let sorted = published.sort( ( a, b ) => - semver.rcompare( semver.coerce( a.tag_name ), semver.coerce( b.tag_name ) ) - ); - - let changelog = sorted.reduce( ( changelog, release ) => - `${changelog} - -### ${release.tag_name} ### - -${release.body}` - , '## Changelog ##' ); - - try { - const results = await replace( { - files: filename, - from: '', - to: changelog, - } ); - - if ( results.filter( result => ! result.hasChanged ).length ) { - console.error( 'No replacements made' ); - process.exitCode = 1; - } - } catch( exception ) { - console.error( exception ); - process.exitCode = 1; - } -} - -run(); diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 15aaa60..26ecab9 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + name: Coding Standards on: push: @@ -8,48 +10,29 @@ on: pull_request: branches: - '**' + schedule: + # Once weekly on Mondays at 03:00 UTC. + # + # ┌───────────── minute (0 - 59) + # │ ┌────────── hour (0 - 23) + # │ │ ┌─────── day of the month (1 - 31) + # │ │ │ ┌──── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌─ day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ │ + - cron: '0 3 * * 1' + workflow_dispatch: jobs: - build: - name: PHP Coding Standards - runs-on: ubuntu-18.04 - steps: - - name: Checkout repository - uses: actions/checkout@v1 - - - name: Composer cache - uses: actions/cache@v1 - with: - path: ~/.composer/cache - key: 7.3-composer-${{ hashFiles('composer.json') }} - - - name: PHPCS cache - uses: actions/cache@v1 - with: - path: tests/cache - # This uses the hash of user-switching.php in its cache key because Actions doesn't support - # always pulling in a cache file and simultaneously always updating it, unlike Travis. - # This way we always pull in a cache file and refresh it with each new version of the plugin. - key: 7.3-phpcs-${{ hashFiles('user-switching.php') }} - - - name: Install PHP - uses: shivammathur/setup-php@2.7.0 - with: - php-version: '7.3' - coverage: none - env: - fail-fast: true - - - name: Debugging - run: | - php --version - composer --version - - - name: Install dependencies - run: | - composer install --prefer-dist - - - name: Run the tests - run: | - composer test:cs - composer test:phpstan + test: + name: PHP / PHP ${{ matrix.php }} + uses: johnbillion/plugin-infrastructure/.github/workflows/reusable-coding-standards.yml@trunk + strategy: + matrix: + php: + - '8.2' + - '7.4' + fail-fast: false + with: + php: ${{ matrix.php }} diff --git a/.github/workflows/deploy-assets.yml b/.github/workflows/deploy-assets.yml index ba20da8..f76d5fe 100644 --- a/.github/workflows/deploy-assets.yml +++ b/.github/workflows/deploy-assets.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + name: Deploy Assets on: push: @@ -7,23 +9,12 @@ on: jobs: wordpress: name: WordPress.org - runs-on: ubuntu-18.04 - steps: - - name: Checkout repository - uses: actions/checkout@v1 - - # @TODO need to cache the npm dependencies - - name: Install Dependencies - run: npm install - - - name: Populate Changelog - run: node .github/workflows/changelog.js readme.md - env: - TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: WordPress Plugin Deploy - uses: 10up/action-wordpress-plugin-asset-update@1.4.1 - env: - SVN_USERNAME: ${{ secrets.WPORG_SVN_USERNAME }} - SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }} - README_NAME: readme.md + uses: johnbillion/plugin-infrastructure/.github/workflows/reusable-deploy-assets.yml@trunk + with: + plugin: user-switching + readme: readme.md + node: false + vendor: false + secrets: + WPORG_SVN_USERNAME: ${{ secrets.WPORG_SVN_USERNAME }} + WPORG_SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }} diff --git a/.github/workflows/deploy-tag.yml b/.github/workflows/deploy-tag.yml index d16abb7..db9752d 100644 --- a/.github/workflows/deploy-tag.yml +++ b/.github/workflows/deploy-tag.yml @@ -1,27 +1,19 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + name: Deploy Tag on: release: types: [published] -jobs: - wordpress: - name: WordPress.org - runs-on: ubuntu-18.04 - steps: - - name: Checkout repository - uses: actions/checkout@v1 - - # @TODO need to cache the npm dependencies - - name: Install Dependencies - run: npm install +concurrency: WordPress.org - - name: Populate Changelog - run: node .github/workflows/changelog.js readme.md - env: - TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: WordPress Plugin Deploy - uses: 10up/action-wordpress-plugin-deploy@1.4.1 - env: - SVN_USERNAME: ${{ secrets.WPORG_SVN_USERNAME }} - SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }} +jobs: + test: + name: Deploy Tag + uses: johnbillion/plugin-infrastructure/.github/workflows/reusable-deploy-tag.yml@trunk + with: + plugin: user-switching + readme: readme.md + secrets: + WPORG_SVN_USERNAME: ${{ secrets.WPORG_SVN_USERNAME }} + WPORG_SVN_PASSWORD: ${{ secrets.WPORG_SVN_PASSWORD }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 5d48c7c..0000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Functional Tests -on: - push: - branches: - - 'develop' - - 'trunk' - - 'master' - pull_request: - branches: - - '**' - # Once weekly on Wednesdays at 06:00 UTC. - schedule: - - cron: '0 6 * * 3' - workflow_dispatch: - -jobs: - build: - strategy: - matrix: - php: ['7.3','8.0'] - wp: ['*', 'dev-nightly'] - fail-fast: false - name: WP ${{ matrix.wp }} / PHP ${{ matrix.php }} - runs-on: ubuntu-18.04 - steps: - - name: Checkout repository - uses: actions/checkout@v1 - - - name: Composer cache - uses: actions/cache@v1 - with: - path: ~/.composer/cache - key: ${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - - - name: Install PHP - uses: shivammathur/setup-php@2.7.0 - with: - php-version: ${{ matrix.php }} - extensions: mysqli, xmlwriter - coverage: none - env: - fail-fast: true - - - name: Debugging - run: | - php --version - php -m - composer --version - mysql --version - - - name: Install dependencies - run: | - sudo systemctl start mysql.service - composer install --prefer-dist --ignore-platform-reqs - composer require --dev --update-with-dependencies --prefer-dist --ignore-platform-reqs roots/wordpress="${{ matrix.wp }} || *" - - - name: Run the tests - run: composer test:ft - env: - MYSQL_DATABASE: wordpress - WP_TESTS_DB_PASS: root diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..917324b --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow + +name: Integration Tests +on: + push: + branches: + - 'develop' + - 'trunk' + - 'master' + pull_request: + branches: + - '**' + schedule: + # Once weekly on Mondays at 05:00 UTC. + # + # ┌───────────── minute (0 - 59) + # │ ┌────────── hour (0 - 23) + # │ │ ┌─────── day of the month (1 - 31) + # │ │ │ ┌──── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌─ day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ │ + - cron: '0 5 * * 1' + workflow_dispatch: + +jobs: + test: + name: WP ${{ matrix.wp }} / PHP ${{ matrix.php }} + uses: johnbillion/plugin-infrastructure/.github/workflows/reusable-integration-tests.yml@trunk + strategy: + matrix: + wp: + - 'nightly' + - '6.4' + - '6.3' + php: + - '8.2' + - '8.1' + - '8.0' + - '7.4' + dev: + - ${{ github.ref_name == 'develop' }} + exclude: + # Only run the nightly tests on the develop branch. + - wp: 'nightly' + dev: false + fail-fast: false + with: + wp: ${{ matrix.wp }} + php: ${{ matrix.php }} + node: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index d5c5266..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Unit Tests -on: - push: - branches: - - 'develop' - - 'trunk' - - 'master' - pull_request: - branches: - - '**' - # Once weekly on Wednesdays at 05:00 UTC. - schedule: - - cron: '0 5 * * 3' - workflow_dispatch: - -jobs: - build: - strategy: - matrix: - php: ['7.3'] - wp: ['*', 'dev-nightly'] - fail-fast: false - name: WP ${{ matrix.wp }} / PHP ${{ matrix.php }} - runs-on: ubuntu-18.04 - steps: - - name: Checkout repository - uses: actions/checkout@v1 - - - name: Composer cache - uses: actions/cache@v1 - with: - path: ~/.composer/cache - key: ${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - - - name: Install PHP - uses: shivammathur/setup-php@2.7.0 - with: - php-version: ${{ matrix.php }} - extensions: mysqli, xmlwriter - coverage: none - env: - fail-fast: true - - - name: Debugging - run: | - php --version - php -m - composer --version - mysql --version - - - name: Install dependencies - run: | - sudo systemctl start mysql.service - composer install --prefer-dist - composer require --dev --update-with-dependencies --prefer-dist roots/wordpress="${{ matrix.wp }} || *" wp-phpunit/wp-phpunit="${{ matrix.wp }} || *" - - - name: Run the tests - run: composer test:ut - env: - MYSQL_DATABASE: wordpress - WP_TESTS_DB_PASS: root diff --git a/.gitignore b/.gitignore index 09fd85e..30cde05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,6 @@ -# Directories -/build -/node_modules -/svn -/tests/wordpress -/vendor - -# Files /composer.lock -/package-lock.json -/tests/.env +/vendor +/node_modules +/tests/_output/ +/tests/_support/_generated +/tests/cache diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..3c03207 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index dff8121..5597c7e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -john@johnblackbourn.com. +https://johnblackbourn.com/about/ . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a64275..f2e2834 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,19 +1,10 @@ -[![Build Status](https://img.shields.io/github/workflow/status/johnbillion/user-switching/Test/develop?style=for-the-badge)](https://github.com/johnbillion/user-switching/actions) -[![](https://img.shields.io/badge/contributor-code%20of%20conduct-5e0d73.svg?style=for-the-badge)](https://github.com/johnbillion/user-switching/blob/develop/CODE_OF_CONDUCT.md) -[![](https://img.shields.io/badge/ethical-open%20source-4baaaa.svg?style=for-the-badge)](#ethical-open-source) +[![](https://img.shields.io/badge/contributor-code%20of%20conduct-5e0d73.svg?style=flat-square)](https://github.com/johnbillion/user-switching/blob/develop/CODE_OF_CONDUCT.md) +[![](https://img.shields.io/badge/ethical-open%20source-4baaaa.svg?style=flat-square)](#ethical-open-source) # Contributing to User Switching Bug reports, code contributions, and general feedback are very welcome. These should be submitted through [the GitHub repository](https://github.com/johnbillion/user-switching). Development happens in the `develop` branch, and any pull requests should be made against that branch please. -* [Reviews on WordPress.org](#reviews-on-wordpressorg) -* [Reporting Security Issues](#reporting-security-issues) -* [Inclusivity and Code of Conduct](#inclusivity-and-code-of-conduct) -* [Setting up Locally](#setting-up-locally) -* [Running the Tests](#running-the-tests) -* [Releasing a New Version](#releasing-a-new-version) -* [Ethical Open Source](#ethical-open-source) - ## Reviews on WordPress.org If you enjoy using User Switching I would greatly appreciate it if you left a positive review on the WordPress.org Plugin Directory. This is the fastest and easiest way to contribute to User Switching 😄. @@ -35,48 +26,74 @@ You can clone this repo and activate it like a normal WordPress plugin. If you w ### Prerequisites * [Composer](https://getcomposer.org/) -* [Node](https://nodejs.org/) -* A local web server running WordPress +* [Docker Desktop](https://www.docker.com/desktop) to run the tests ### Setup -1. Install the PHP dependencies: +Install the PHP dependencies: - composer install + composer install -2. Install the Node dependencies: +## Running the Tests - npm install +The test suite includes integration and acceptance tests which run in a Docker container. Ensure Docker Desktop is running, then start the containers with: -3. Check the MySQL database credentials in the `tests/.env` file and amend them as necessary. + composer test:start -**Important:** Ensure you use a separate test database (eg. `wordpress_test`) because, just like the WordPress test suite, the database will be wiped clean with every test run. +To run the whole test suite which includes integration tests, acceptance tests, linting, and static analysis: -## Running the Tests + composer test -To run the whole test suite which includes PHPUnit unit tests, PHPCS code sniffs, PHPStan static analysis, and WordHat functional tests: +To run tests individually, run one of: - composer test + composer test:integration + composer test:acceptance + composer test:phpcs + composer test:phpstan -To run just the unit tests: +To stop the Docker containers: - composer test:ut + composer test:stop -To run just the code sniffs: +## Releasing a New Version - composer test:cs +These are the steps to take to release a new version of User Switching (for contributors who have push access to the GitHub repo). -To run just the static analysis: +### Prior to Release - composer test:phpstan +1. Check [the milestone on GitHub](https://github.com/johnbillion/user-switching/milestones) for open issues or PRs. Fix or reassign as necessary. +1. If this is a non-patch release, check issues and PRs assigned to the patch or minor milestones that will get skipped. Reassign as necessary. +1. Ensure you're on the `develop` branch and all the changes for this release have been merged in. +1. Ensure `readme.md` contains up to date descriptions, "Tested up to" versions, FAQs, screenshots, etc. +1. Ensure `.gitattributes` is up to date with all files that shouldn't be part of the build. + - To do this, run `git archive --output=user-switching.zip HEAD` then check the contents for files that shouldn't be part of the package. +1. Run `composer test` and ensure everything passes. +1. Run `git push origin develop` (if necessary) and ensure CI is passing. +1. Prepare a changelog for [the Releases page on GitHub](https://github.com/johnbillion/user-switching/releases). -To run just the functional tests: +### For Release - composer test:ft +1. Bump the plugin version number: + - `npm run bump:patch` for a patch release (1.2.3 => 1.2.4) + - `npm run bump:minor` for a minor release (1.2.3 => 1.3.0) + - `npm run bump:major` for a major release (1.2.3 => 2.0.0) +1. `git push origin develop` +1. `git tag x.y.z` +1. `git push origin --tags` +1. Enter the changelog into [the release on GitHub](https://github.com/johnbillion/user-switching/releases) and publish it. -## Releasing a New Version +### Post Release + +Publishing a release on GitHub triggers an action which deploys the release to the WordPress.org Plugin Directory. No need to touch Subversion. + +New milestones are automatically created for the next major, minor, and patch releases where appropriate. + +1. Close the milestone. +1. If this is a non-patch release, manually delete any [unused patch and minor milestones on GitHub](https://github.com/johnbillion/user-switching/milestones). +1. Check the new version has appeared [on the WordPress.org plugin page](https://wordpress.org/plugins/user-switching/) (it'll take a few minutes). +1. Resolve relevant threads on [the plugin's support forums](https://wordpress.org/support/plugin/user-switching/). -User Switching gets automatically deployed to the WordPress.org Plugin Directory whenever a new release is published on GitHub. +### Asset Updates Assets such as screenshots and banners are stored in the `.wordpress-org` directory. These get deployed as part of the automated release process too. diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 047844a..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,106 +0,0 @@ -module.exports = function(grunt) { - 'use strict'; - - require('load-grunt-tasks')(grunt); - - var pkg = grunt.file.readJSON('package.json'); - var config = {}; - - config.pkg = pkg; - - config['convert-svg-to-png'] = { - normal: { - options: { - size: { - w: '128px', - h: '128px' - } - }, - files: [ - { - expand: true, - src: [ - '.wordpress-org/icon.svg' - ], - dest: '.wordpress-org/128' - } - ] - }, - retina: { - options: { - size: { - w: '256px', - h: '256px' - } - }, - files: [ - { - src: [ - '.wordpress-org/icon.svg' - ], - dest: '.wordpress-org/256' - } - ] - } - }; - - config.clean = { - icons: Object.keys(config['convert-svg-to-png']).map(function(key){ - return config['convert-svg-to-png'][ key ].files[0].dest; - }) - }; - - config.rename = { - icons:{ - expand: true, - src: [ - '.wordpress-org/*/icon.png' - ], - rename: function (dest,src) { - return src.replace(/.wordpress-org\/(\d+)\/icon.png/,'.wordpress-org/icon-$1x$1.png'); - } - } - }; - - config.version = { - main: { - options: { - prefix: 'Version:[\\s]+' - }, - src: [ - '<%= pkg.name %>.php' - ] - }, - readme: { - options: { - prefix: 'Stable tag:[\\s]+' - }, - src: [ - 'readme.md' - ] - }, - pkg: { - src: [ - 'package.json' - ] - } - }; - - grunt.initConfig(config); - - grunt.registerTask('bump', function(version) { - if ( ! version ) { - grunt.fail.fatal( 'No version specified. Usage: bump:major, bump:minor, bump:patch, bump:x.y.z' ); - } - - grunt.task.run([ - 'version::' + version - ]); - }); - - grunt.registerTask('icons', [ - 'convert-svg-to-png', - 'rename:icons', - 'clean:icons' - ]); -}; diff --git a/behat.yml b/behat.yml deleted file mode 100644 index 8bc895d..0000000 --- a/behat.yml +++ /dev/null @@ -1,35 +0,0 @@ -default: - suites: - default: - contexts: - - UserSwitchingContext - - PaulGibbs\WordpressBehatExtension\Context\WordpressContext - - Behat\MinkExtension\Context\MinkContext - - PaulGibbs\WordpressBehatExtension\Context\DashboardContext - - PaulGibbs\WordpressBehatExtension\Context\SiteContext - - PaulGibbs\WordpressBehatExtension\Context\UserContext - - FailAid\Context\FailureContext - - extensions: - Behat\MinkExtension: - base_url: 'http://localhost:8000' - browser_name: chrome - default_session: default - sessions: - default: - goutte: - guzzle_parameters: - verify: false # Allow self-signed SSL certificates - - PaulGibbs\WordpressBehatExtension: - default_driver: wpphp - users: - - - roles: - - administrator - username: admin - password: admin - database: - restore_after_test: true - - FailAid\Extension: ~ diff --git a/bin/test.sh b/bin/test.sh deleted file mode 100755 index 14bb611..0000000 --- a/bin/test.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -# Specify the directory where the WordPress installation lives: -WP_CORE_DIR="${PWD}/tests/wordpress" - -# Specify the URL for the site: -WP_URL="localhost:8000" - -# Shorthand: -WP="./vendor/bin/wp --color --path=$WP_CORE_DIR --url=http://$WP_URL" - -# Start the PHP server: -php -S "$WP_URL" -t "$WP_CORE_DIR" -d disable_functions=mail 2>/dev/null & - -# Reset or install the test database: -$WP db reset --yes - -# Install WordPress: -$WP core install --title="Example" --admin_user="admin" --admin_password="admin" --admin_email="admin@example.com" - -# Install language files: -$WP language core install it_IT -$WP language plugin install user-switching it_IT - -# Run the functional tests: -BEHAT_PARAMS='{"extensions" : {"PaulGibbs\\WordpressBehatExtension" : {"path" : "'$WP_CORE_DIR'"}}}' \ - ./vendor/bin/behat --colors "$1" - -# Stop the PHP web server: -kill $! diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100644 index 0000000..40f2274 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,9 @@ +paths: + tests: tests + output: tests/_output + data: tests/_data + support: tests/_support + envs: tests/_envs +actor_suffix: Tester +params: + - env diff --git a/composer.json b/composer.json index a732e71..d1c3094 100644 --- a/composer.json +++ b/composer.json @@ -1,80 +1,95 @@ { - "name" : "johnbillion/user-switching", + "name": "johnbillion/user-switching", "description": "Instant switching between user accounts in WordPress.", - "homepage" : "https://github.com/johnbillion/user-switching", - "type" : "wordpress-plugin", - "license" : "GPL-2.0-or-later", - "authors" : [ + "license": "GPL-2.0-or-later", + "type": "wordpress-plugin", + "authors": [ { - "name" : "John Blackbourn", + "name": "John Blackbourn", "homepage": "https://johnblackbourn.com/" } ], + "homepage": "https://github.com/johnbillion/user-switching/", "support": { "issues": "https://github.com/johnbillion/user-switching/issues", "forum": "https://wordpress.org/support/plugin/user-switching", "source": "https://github.com/johnbillion/user-switching" }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/johnbillion" + } + ], + "require": { + "php": ">=7.4", + "composer/installers": "^1.0 || ^2.0" + }, + "require-dev": { + "codeception/module-asserts": "^1.0", + "codeception/module-db": "^1.0", + "codeception/module-webdriver": "^1.0", + "codeception/util-universalframework": "^1.0", + "johnbillion/plugin-infrastructure": "dev-trunk", + "lucatume/wp-browser": "3.2.1", + "phpcompatibility/phpcompatibility-wp": "2.1.4", + "phpstan/phpstan": "1.10.41", + "phpstan/phpstan-phpunit": "1.3.15", + "roots/wordpress-core-installer": "^1.0.0", + "roots/wordpress-full": "*", + "szepeviktor/phpstan-wordpress": "1.3.2", + "wp-coding-standards/wpcs": "3.0.1" + }, + "autoload-dev": { + "psr-4": { + "UserSwitching\\Tests\\": "tests/integration" + } + }, "config": { + "allow-plugins": { + "composer/installers": true, + "roots/wordpress-core-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "classmap-authoritative": true, + "preferred-install": "dist", + "prepend-autoloader": false, "sort-packages": true }, "extra": { - "wordpress-install-dir": "tests/wordpress" - }, - "require": { - "php": ">=5.3", - "composer/installers": "^1" - }, - "require-dev" : { - "behat/mink-goutte-driver": "^1.2", - "dealerdirect/phpcodesniffer-composer-installer": "0.7.0", - "genesis/behat-fail-aid": "^2.0", - "johnbillion/php-docs-standards": "^2", - "paulgibbs/behat-wordpress-extension": "^3.3", - "php-stubs/wordpress-stubs": "^5.7", - "phpcompatibility/php-compatibility": "^9", - "phpstan/phpstan": "0.12.87", - "phpstan/phpstan-deprecation-rules": "0.12.6", - "phpstan/phpstan-phpunit": "0.12.19", - "phpunit/phpunit": "^7", - "roots/wordpress": "*", - "szepeviktor/phpstan-wordpress": "^0.7.5", - "vlucas/phpdotenv": "^3", - "wp-cli/core-command": "^2", - "wp-cli/db-command": "^2", - "wp-cli/language-command": "^2", - "wp-coding-standards/wpcs": "^2", - "wp-phpunit/wp-phpunit": "*" + "wordpress-install-dir": "vendor/wordpress/wordpress" }, "scripts": { - "post-update-cmd": [ - "@php -r \"! file_exists( 'tests/.env' ) && copy( 'tests/.env.dist', 'tests/.env' );\"" + "test": [ + "@composer validate --strict --no-check-lock", + "@test:phpstan", + "@test:phpcs", + "@test:start", + "@test:integration", + "@test:acceptance", + "@test:stop" ], - "test:cs": [ - "phpcs -nps --colors --report-code --report-summary --report-width=80 ." + "test:acceptance": [ + "acceptance-tests --cli=\"language core install it_IT\" --cli=\"language plugin install user-switching it_IT\"" ], - "test:phpstan": [ - "phpstan analyze" + "test:destroy": [ + "tests-destroy" ], - "test:ut": [ - "wp db reset --yes --path=tests/wordpress #", - "export WP_MULTISITE=0 && phpunit --verbose --colors=always --exclude-group=ms-required", - "export WP_MULTISITE=1 && phpunit --verbose --colors=always --exclude-group=ms-excluded" + "test:integration": [ + "integration-tests" ], - "test:ft": [ - "bin/test.sh" + "test:phpcs": [ + "phpcs -ps --colors --report-code --report-summary --report-width=80 --basepath='./' ." ], - "test": [ - "@test:cs", - "@test:phpstan", - "@test:ut", - "@test:ft" + "test:phpstan": [ + "codecept build", + "phpstan analyze --memory-limit=1024M" + ], + "test:start": [ + "tests-start" + ], + "test:stop": [ + "tests-stop" ] - }, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/johnbillion" - } - ] + } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..69d0be5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3.1' + +include: + - + path: vendor/johnbillion/plugin-infrastructure/tests/docker-compose.yml + project_directory: . diff --git a/features/bootstrap/UserSwitchingContext.php b/features/bootstrap/UserSwitchingContext.php deleted file mode 100644 index b88e3d0..0000000 --- a/features/bootstrap/UserSwitchingContext.php +++ /dev/null @@ -1,174 +0,0 @@ -[^"]+)"$/ - */ - public function switch_to_user( $user_id ) { - $user_id = $this->getUserIdFromLogin( $user_id ); - - Assert::assertNotEmpty( $user_id ); - - $this->visitPath( sprintf( 'wp-admin/user-edit.php?user_id=%d', $user_id ) ); - $this->getSession()->getPage()->clickLink( 'user_switching_switcher' ); - } - - /** - * Switch off - * - * @When /^(?:|I )switch off$/ - */ - public function switch_off() { - $this->getSession()->getPage()->clickLink( "Switch Off" ); - } - - /** - * Switch back to the original user - * - * @param string $user_id - * - * @When /^(?:|I )switch back to "(?P[^"]+)"$/ - */ - public function switch_back( $user_id ) { - $display_name = $this->getUserDataFromUsername( 'display_name', $user_id ); - - Assert::assertNotEmpty( $user_id ); - Assert::assertNotEmpty( $display_name ); - - $this->getSession()->getPage()->clickLink( sprintf( - 'Switch back to %1$s (%2$s)', - $display_name, - $user_id - ) ); - } - - /** - * Verify that the user is logged in as the specified user - * - * @param string $user_id - * - * @Then /^(?:|I )should be logged in as "(?P[^"]+)"$/ - * - * @throws ElementNotFoundException If the display name could not be found. - * @throws ElementTextException If the display name is incorrect. - */ - public function logged_in_as( $user_id ) { - $display_name = $this->getUserDataFromUsername( 'display_name', $user_id ); - - Assert::assertNotEmpty( $display_name ); - - $this->visitPath( '/' ); - - $browser = $this->getSession(); - $selector = '#wpadminbar .display-name'; - $element = $browser->getPage()->find( 'css', $selector ); - - if ( ! $element ) { - throw new ElementNotFoundException( - $browser->getDriver(), - 'element', - 'css', - $selector - ); - } - - if ( $display_name !== $element->getText() ) { - throw new ElementTextException( - sprintf( - 'The user is logged in as "%s"', - $element->getText() - ), - $browser->getDriver(), - $element - ); - } - } - - /** - * Verify that the user is logged out - * - * @Then /^(?:|I )should be logged out$/ - * - * @throws ExpectationException If the user is not logged out. - */ - public function logged_out() { - $this->visitPath( '/' ); - - $browser = $this->getSession(); - $selector = '#wpadminbar .display-name'; - $element = $browser->getPage()->find( 'css', $selector ); - - if ( $element ) { - throw new ExpectationException( - 'The user is not logged out', - $browser->getDriver() - ); - } - } - - /** - * Verify the page language - * - * @param string $lang - * - * @Then /^the page language should be "(?P[^"]+)"$/ - * - * @throws ElementHtmlException If the language is incorrect. - */ - public function thePageLanguageShouldBe( $lang ) { - $this->theElementLanguageShouldBe( 'html', $lang ); - } - - /** - * Verify the language of an element - * - * @param string $selector - * @param string $lang - * - * @Then /^the "(?P[^"]+)" element language should be "(?P[^"]+)"$/ - * - * @throws ElementHtmlException If the language is incorrect. - */ - public function theElementLanguageShouldBe( $selector, $lang ) { - $browser = $this->getSession(); - $element = $browser->getPage()->find( 'css', $selector ); - - if ( ! $element ) { - throw new ElementNotFoundException( - $browser->getDriver(), - 'element', - 'css', - $selector - ); - } - - if ( $lang !== $element->getAttribute( 'lang' ) ) { - throw new ElementHtmlException( - sprintf( - 'The language is "%s" instead of "%s"', - $element->getAttribute( 'lang' ), - $lang - ), - $browser->getDriver(), - $element - ); - } - } -} diff --git a/features/locales.feature b/features/locales.feature deleted file mode 100644 index 67f2cae..0000000 --- a/features/locales.feature +++ /dev/null @@ -1,22 +0,0 @@ -Feature: Locale support - As an administrator of a site which uses more than one language - I need to be able to switch to user accounts that use a different language - And see User Switching's output in my original language - - Background: - Given the "user-switching/user-switching.php" plugin is active - And there are users: - | user_login | display_name | user_email | user_pass | role | locale | - | autore | Autore | autore@example.com | password | author | it_IT | - - Scenario: Switch from English admin to Italian author and back - Given I am logged in as admin - When I switch to user "autore" - Then the page language should be "it-IT" - But I should see a status message that says "Switched to Autore" - And the "#user_switching p" element language should be "en-US" - - When I go to the dashboard - And I switch back to "admin" - Then the page language should be "en-US" - And I should see a status message that says "Switched back to admin" diff --git a/features/locales2.feature b/features/locales2.feature deleted file mode 100644 index 5d52c5b..0000000 --- a/features/locales2.feature +++ /dev/null @@ -1,23 +0,0 @@ -Feature: Locale support - As an administrator of a site which uses more than one language - I need to be able to switch between users - And see User Switching's output in my original language - - Background: - Given the "user-switching/user-switching.php" plugin is active - And there are users: - | user_login | display_name | user_email | user_pass | role | locale | - | admin_it | Admin IT | admin_it@example.com | password | administrator | it_IT | - | author_en | Author EN | author_en@example.com | password | author | | - - Scenario: Switch from Italian admin to English author and back - Given I am logged in as admin_it - When I switch to user "author_en" - Then the page language should be "en-US" - But I should see a status message that says "Cambiato a Author EN" - And the "#user_switching p" element language should be "it-IT" - - When I go to the dashboard - And I switch back to "admin_it" - Then the page language should be "it-IT" - And I should see a status message that says "Tornato a Admin IT" diff --git a/features/switch-user.feature b/features/switch-user.feature deleted file mode 100644 index 770d2d1..0000000 --- a/features/switch-user.feature +++ /dev/null @@ -1,29 +0,0 @@ -Feature: Switch users - As an administrator - I need to be able to switch between users - In order to access different user accounts - - Background: - Given the "user-switching/user-switching.php" plugin is active - And there are users: - | user_login | display_name | user_email | user_pass | role | - | editor | Editor | editor@example.com | password | editor | - - Scenario: Switch to editor and back - Given I am logged in as admin - When I switch to user "editor" - Then I should see a status message that says "Switched to editor" - And I should be logged in as "editor" - - When I go to the dashboard - And I switch back to "admin" - Then I should see a status message that says "Switched back to admin" - And I should be logged in as "admin" - - Scenario: Switch off and back - Given I am logged in as admin - When I switch off - Then I should be logged out - - When I switch back to "admin" - Then I should be logged in as "admin" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b74d934 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2865 @@ +{ + "name": "user-switching", + "version": "1.7.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "user-switching", + "version": "1.7.0", + "license": "GPL-2.0-or-later", + "devDependencies": { + "plugin-infrastructure": "file:vendor/johnbillion/plugin-infrastructure", + "version-bump-prompt": "^6.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@actions/github": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/http-client": "^1.0.3", + "@octokit/graphql": "^4.3.1", + "@octokit/rest": "^16.43.1" + } + }, + "node_modules/@actions/http-client": { + "version": "1.0.11", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6" + } + }, + "node_modules/@jsdevtools/ez-spawn": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-me-maybe": "^1.0.1", + "cross-spawn": "^7.0.3", + "string-argv": "^0.3.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jsdevtools/version-bump-prompt": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ez-spawn": "^3.0.4", + "command-line-args": "^5.1.1", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "globby": "^11.0.1", + "inquirer": "^7.3.3", + "log-symbols": "^4.0.0", + "semver": "^7.3.2" + }, + "bin": { + "bump": "bin/bump.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "4.2.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/auth-token": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/endpoint": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/graphql": { + "version": "5.0.6", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "18.0.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@octokit/core/node_modules/@octokit/request": { + "version": "6.2.8", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/request-error": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "9.3.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^2.0.1" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "2.16.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^2.0.1", + "deprecation": "^2.3.1" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "2.16.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest": { + "version": "16.43.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^2.4.0", + "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "2.4.0", + "@octokit/request": "^5.2.0", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^2.0.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^4.0.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest/node_modules/@octokit/types": { + "version": "2.16.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/@octokit/rest/node_modules/universal-user-agent": { + "version": "4.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "os-name": "^3.1.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/@types/node": { + "version": "20.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-back": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/atob-lite": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/btoa-lite": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/command-line-args": { + "version": "5.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-replace": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "7.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/macos-release": { + "version": "2.5.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "license": "ISC" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.6.11", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/octokit-pagination-methods": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-name": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plugin-infrastructure": { + "resolved": "vendor/johnbillion/plugin-infrastructure", + "link": true + }, + "node_modules/pump": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/replace-in-file": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^3.0.0", + "glob": "^7.1.6", + "yargs": "^15.0.2" + }, + "bin": { + "replace-in-file": "bin/cli.js" + } + }, + "node_modules/replace-in-file/node_modules/chalk": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.5.3", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "1.14.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typical": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/version-bump-prompt": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/version-bump-prompt": "6.1.0" + }, + "bin": { + "bump": "bump.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/windows-release": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^1.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "4.0.3", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "vendor/johnbillion/plugin-infrastructure": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/github": "^2", + "replace-in-file": "^5", + "semver": "^7" + }, + "engines": { + "node": ">=16" + } + } + }, + "dependencies": { + "@actions/github": { + "version": "2.2.0", + "dev": true, + "requires": { + "@actions/http-client": "^1.0.3", + "@octokit/graphql": "^4.3.1", + "@octokit/rest": "^16.43.1" + } + }, + "@actions/http-client": { + "version": "1.0.11", + "dev": true, + "requires": { + "tunnel": "0.0.6" + } + }, + "@jsdevtools/ez-spawn": { + "version": "3.0.4", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "cross-spawn": "^7.0.3", + "string-argv": "^0.3.1", + "type-detect": "^4.0.8" + } + }, + "@jsdevtools/version-bump-prompt": { + "version": "6.1.0", + "dev": true, + "requires": { + "@jsdevtools/ez-spawn": "^3.0.4", + "command-line-args": "^5.1.1", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "globby": "^11.0.1", + "inquirer": "^7.3.3", + "log-symbols": "^4.0.0", + "semver": "^7.3.2" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@octokit/auth-token": { + "version": "2.5.0", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "4.2.4", + "dev": true, + "peer": true, + "requires": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "@octokit/auth-token": { + "version": "3.0.4", + "dev": true, + "peer": true + }, + "@octokit/endpoint": { + "version": "7.0.6", + "dev": true, + "peer": true, + "requires": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "5.0.6", + "dev": true, + "peer": true, + "requires": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "18.0.0", + "dev": true, + "peer": true + }, + "@octokit/request": { + "version": "6.2.8", + "dev": true, + "peer": true, + "requires": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "3.0.3", + "dev": true, + "peer": true, + "requires": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "9.3.2", + "dev": true, + "peer": true, + "requires": { + "@octokit/openapi-types": "^18.0.0" + } + } + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "dev": true, + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "12.11.0", + "dev": true + }, + "@octokit/plugin-paginate-rest": { + "version": "1.1.2", + "dev": true, + "requires": { + "@octokit/types": "^2.0.1" + }, + "dependencies": { + "@octokit/types": { + "version": "2.16.2", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "dev": true, + "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "2.4.0", + "dev": true, + "requires": { + "@octokit/types": "^2.0.1", + "deprecation": "^2.3.1" + }, + "dependencies": { + "@octokit/types": { + "version": "2.16.2", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/request": { + "version": "5.6.3", + "dev": true, + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "16.43.2", + "dev": true, + "requires": { + "@octokit/auth-token": "^2.4.0", + "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "2.4.0", + "@octokit/request": "^5.2.0", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^2.0.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^4.0.0" + }, + "dependencies": { + "@octokit/request-error": { + "version": "1.2.1", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "2.16.2", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + }, + "universal-user-agent": { + "version": "4.0.1", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } + } + } + }, + "@octokit/types": { + "version": "6.41.0", + "dev": true, + "requires": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "@types/node": { + "version": "20.3.1", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "array-back": { + "version": "3.1.0", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "dev": true + }, + "atob-lite": { + "version": "2.0.0", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "dev": true + }, + "before-after-hook": { + "version": "2.2.3", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "btoa-lite": { + "version": "1.0.0", + "dev": true + }, + "call-me-maybe": { + "version": "1.0.2", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "command-line-args": { + "version": "5.2.1", + "dev": true, + "requires": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "decamelize": { + "version": "1.2.0", + "dev": true + }, + "deprecation": { + "version": "2.3.1", + "dev": true + }, + "detect-indent": { + "version": "6.1.0", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "dev": true + }, + "execa": { + "version": "1.0.0", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "dev": true + }, + "semver": { + "version": "5.7.1", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "dev": true + }, + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-glob": { + "version": "3.2.12", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.15.0", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "figures": { + "version": "3.2.0", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "fill-range": { + "version": "7.0.1", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-replace": { + "version": "3.0.0", + "dev": true, + "requires": { + "array-back": "^3.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.2.3", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.1.0", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "5.2.4", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "dev": true + }, + "inquirer": { + "version": "7.3.3", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "is-extglob": { + "version": "2.1.1", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "dev": true + }, + "is-plain-object": { + "version": "5.0.0", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "dev": true + }, + "lodash.set": { + "version": "4.3.2", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "macos-release": { + "version": "2.5.1", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mute-stream": { + "version": "0.0.8", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "dev": true + }, + "node-fetch": { + "version": "2.6.11", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "dev": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "dev": true + } + } + }, + "octokit-pagination-methods": { + "version": "1.1.0", + "dev": true + }, + "once": { + "version": "1.4.0", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os-name": { + "version": "3.1.0", + "dev": true, + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "dev": true + }, + "plugin-infrastructure": { + "version": "file:vendor/johnbillion/plugin-infrastructure", + "requires": { + "@actions/github": "^2", + "replace-in-file": "^5", + "semver": "^7" + } + }, + "pump": { + "version": "3.0.0", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "queue-microtask": { + "version": "1.2.3", + "dev": true + }, + "replace-in-file": { + "version": "5.0.2", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "glob": "^7.1.6", + "yargs": "^15.0.2" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "reusify": { + "version": "1.0.4", + "dev": true + }, + "run-async": { + "version": "2.4.1", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "6.6.7", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "dev": true + }, + "semver": { + "version": "7.5.3", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "dev": true + }, + "slash": { + "version": "3.0.0", + "dev": true + }, + "string-argv": { + "version": "0.3.2", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "dev": true + }, + "tunnel": { + "version": "0.0.6", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "dev": true + }, + "typical": { + "version": "4.0.0", + "dev": true + }, + "universal-user-agent": { + "version": "6.0.0", + "dev": true + }, + "version-bump-prompt": { + "version": "6.1.0", + "dev": true, + "requires": { + "@jsdevtools/version-bump-prompt": "6.1.0" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.1", + "dev": true + }, + "windows-release": { + "version": "3.3.3", + "dev": true, + "requires": { + "execa": "^1.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json index a29a179..20a65d0 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,20 @@ { - "name": "user-switching", - "version": "1.5.8", - "description": "Instant switching between user accounts in WordPress.", - "license": "GPL-2.0-or-later", - "author": "John Blackbourn", - "repository": "johnbillion/user-switching", - "devDependencies": { - "@actions/github": "^2", - "grunt": "^1", - "grunt-contrib-clean": "^2", - "grunt-convert-svg-to-png": "^1", - "grunt-rename-util": "^1", - "grunt-version": "^1", - "load-grunt-tasks": "^4", - "replace-in-file": "^5", - "semver": "^7" - }, - "scripts": { - "grunt": "grunt" - } + "name": "user-switching", + "version": "1.7.1", + "description": "Instant switching between user accounts in WordPress.", + "license": "GPL-2.0-or-later", + "author": "John Blackbourn", + "repository": "johnbillion/user-switching", + "engines": { + "node": ">=18" + }, + "devDependencies": { + "plugin-infrastructure": "file:vendor/johnbillion/plugin-infrastructure", + "version-bump-prompt": "^6.1.0" + }, + "scripts": { + "bump:patch": "bump patch --commit 'Version %s.' user-switching.php package.json package-lock.json readme.md", + "bump:minor": "bump minor --commit 'Version %s.' user-switching.php package.json package-lock.json readme.md", + "bump:major": "bump major --commit 'Version %s.' user-switching.php package.json package-lock.json readme.md" + } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6d41c08..47a202f 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -1,60 +1,58 @@ + - + + - */.github/* - */build/* - */features/* - */node_modules/* - */tests/* - */vendor/* - Gruntfile.js + /tests/* + /vendor/* - - - - - - - - - + + + + - + + + - + + + - + - + + + + - diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 100b8fb..a0ee43d 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,32 +1,24 @@ includes: - - vendor/phpstan/phpstan-deprecation-rules/rules.neon - - vendor/phpstan/phpstan-phpunit/extension.neon - - vendor/szepeviktor/phpstan-wordpress/extension.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/szepeviktor/phpstan-wordpress/extension.neon + - vendor/johnbillion/plugin-infrastructure/phpstan/extension.neon parameters: - level: max - implicitThrows: false - paths: - - user-switching.php - - tests - excludePaths: - analyse: - - tests/stubs.php - - tests/wordpress - - tests/wp-config.php - - tests/wp-tests-config.php - scanDirectories: - - tests - - vendor/johnbillion/php-docs-standards - - vendor/wp-phpunit/wp-phpunit/includes - scanFiles: - - user-switching.php - bootstrapFiles: - - tests/stubs.php - ignoreErrors: - # Uses func_get_args() - - '#^Function apply_filters invoked with [34567] parameters, 2 required\.$#' - # Covers the breaks after exits in user_switching::action_init() - - - message: '#^Unreachable statement#' - path: user-switching.php - count: 3 + level: 9 + paths: + - user-switching.php + - tests/acceptance + - tests/integration + scanDirectories: + - tests/_support/ + - vendor/lucatume/wp-browser/src/includes/factory/ + excludePaths: + analyse: + - tests/integration/Supports/ + bootstrapFiles: + - tests/phpstan/stubs.php + ignoreErrors: + # Covers the breaks after exits in user_switching::action_init() + - + message: '#^Unreachable statement#' + path: user-switching.php + count: 3 diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index d69be5c..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - ./tests/ - - - - - user-switching.php - - - diff --git a/readme.md b/readme.md index 65d2d3a..7239103 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,9 @@ # User Switching -Stable tag: 1.5.8 -Requires at least: 3.7 -Tested up to: 6.0 -Requires PHP: 5.3 +Stable tag: 1.7.1 +Tested up to: 6.4 License: GPL v2 or later -Tags: users, profiles, user switching, fast user switching, multisite, buddypress, bbpress, become, user management, developer +Tags: users, user switching, fast user switching, multisite, woocommerce, buddypress, bbpress Contributors: johnbillion Donate link: https://github.com/sponsors/johnbillion @@ -13,21 +11,24 @@ Donate link: https://github.com/sponsors/johnbillion Instant switching between user accounts in WordPress. -[![](https://img.shields.io/badge/ethical-open%20source-4baaaa.svg?style=for-the-badge)](#ethical-open-source) -[![](https://img.shields.io/wordpress/plugin/installs/user-switching?style=for-the-badge)](https://wordpress.org/plugins/user-switching/) -[![](https://img.shields.io/github/workflow/status/johnbillion/user-switching/Test/develop?style=for-the-badge)](https://github.com/johnbillion/user-switching/actions) +[![](https://img.shields.io/badge/ethical-open%20source-4baaaa.svg?style=flat-square)](#ethical-open-source) +[![](https://img.shields.io/wordpress/plugin/installs/user-switching?style=flat-square)](https://wordpress.org/plugins/user-switching/) +[![](https://img.shields.io/github/actions/workflow/status/johnbillion/user-switching/integration-tests.yml?branch=develop&style=flat-square)](https://github.com/johnbillion/user-switching/actions) ## Description -This plugin allows you to quickly swap between user accounts in WordPress at the click of a button. You'll be instantly logged out and logged in as your desired user. This is handy for test environments where you regularly log out and in between different accounts, or for administrators who need to switch between multiple accounts. +This plugin allows you to quickly swap between user accounts in WordPress at the click of a button. You'll be instantly logged out and logged in as your desired user. This is handy for testing environments, for helping customers on WooCommerce sites, or for any site where administrators need to switch between multiple accounts. ### Features * Switch user: Instantly switch to any user account from the *Users* screen. * Switch back: Instantly switch back to your originating account. * Switch off: Log out of your account but retain the ability to instantly switch back in again. - * Switching between users is secure (see the *Security* section below). - * Compatible with WordPress, WordPress Multisite, WooCommerce, BuddyPress, bbPress, and most two-factor authentication plugins. + * Compatible with Multisite, WooCommerce, BuddyPress, and bbPress. + * Compatible with most two-factor authentication solutions (see the [FAQ](https://wordpress.org/plugins/user-switching/faq/) for more info). + * Approved for use on enterprise-grade WordPress platforms such as Altis and WordPress VIP. + +Note: User Switching supports versions of WordPress up to three years old, and PHP version 7.4 or higher. ### Security @@ -36,7 +37,7 @@ This plugin allows you to quickly swap between user accounts in WordPress at the * Uses the cookie authentication system in WordPress when remembering the account(s) you've switched from and when switching back. * Implements the nonce security system in WordPress, meaning only those who intend to switch users can switch. * Full support for user session validation where appropriate. - * Full support for administration over SSL (if applicable). + * Full support for HTTPS. ### Usage @@ -55,7 +56,7 @@ I maintain several other plugins for developers. Check them out: ### Privacy Statement -User Switching makes use of browser cookies in order to allow users to switch to another account. Its cookies operate using the same mechanism as the authentication cookies in WordPress core, therefore their values contain the user's `user_login` field in plain text which should be treated as potentially personally identifiable information. The names of the cookies are: +User Switching makes use of browser cookies in order to allow users to switch to another account. Its cookies operate using the same mechanism as the authentication cookies in WordPress core, which means their values contain the user's `user_login` field in plain text which should be treated as potentially personally identifiable information (PII) for privacy and regulatory reasons (GDPR, CCPA, etc). The names of the cookies are: * `wordpress_user_sw_{COOKIEHASH}` * `wordpress_user_sw_secure_{COOKIEHASH}` @@ -65,40 +66,43 @@ User Switching does not send data to any third party, nor does it include any th See also the FAQ for some questions relating to privacy and safety when switching between users. -### Ethical Open Source +### Accessibility Statement -User Switching is considered **Ethical Open Source** because it meets all of the criteria of [The Ethical Source Definition (ESD)](https://ethicalsource.dev/definition/): +User Switching aims to be fully accessible to all of its users. It implements best practices for web accessibility, outputs semantic and structured markup, adheres to the default styles and accessibility guidelines of WordPress, uses the accessibility APIs provided by WordPress and web browsers where appropriate, and is fully accessible via keyboard. -1. It benefits the commons. -2. It is created in the open. -3. Its community is welcoming and just. -4. It puts accessibility first. -5. It prioritizes user safety. -6. It protects user privacy. -7. It encourages fair compensation. +User Switching should adhere to Web Content Accessibility Guidelines (WCAG) 2.0 at level AA when used with a recent version of WordPress where its admin area itself adheres to these guidelines. If you've experienced or identified an accessibility issue in User Switching, please open a thread in [the User Switching plugin support forum](https://wordpress.org/support/plugin/user-switching/) and I'll address it swiftly. ## Screenshots -1. The *Switch To* link on the Users screen
![The Switch To link on the Users screen](.wordpress-org/screenshot-1.png) - -2. The *Switch To* link on a user's profile
![The Switch To link on a user's profile](.wordpress-org/screenshot-2.png) +1. The *Switch To* link on the Users screen + ![The Switch To link on the Users screen](.wordpress-org/screenshot-1.png) +2. The *Switch To* link on a user's profile + ![The Switch To link on a user's profile](.wordpress-org/screenshot-2.png) ## Frequently Asked Questions ### Does this plugin work with PHP 8? -Yes. +Yes, it's actively tested and working up to PHP 8.2. ### What does "Switch off" mean? Switching off logs you out of your account but retains your user ID in an authentication cookie so you can switch straight back without having to log in again manually. It's akin to switching to no user, and being able to switch back. -The *Switch Off* link can be found in your profile menu in the WordPress toolbar. Once you've switched off you'll see a *Switch back* link on the Log In screen and in the footer of your site. +The *Switch Off* link can be found in your profile menu in the WordPress toolbar. Once you've switched off you'll see a *Switch back* link in a few places: + +* In the footer of your site +* On the Log In screen +* In the "Meta" widget ### Does this plugin work with WordPress Multisite? Yes, and you'll also be able to switch users from the Users screen in Network Admin. +### Does this plugin work with WooCommerce? + +Yes, and you'll also be able to switch users from various WooCommerce administration screens. + ### Does this plugin work with BuddyPress? Yes, and you'll also be able to switch users from member profile screens and the member listing screen. @@ -107,10 +111,6 @@ Yes, and you'll also be able to switch users from member profile screens and the Yes, and you'll also be able to switch users from member profile screens. -### Does this plugin work with WooCommerce? - -Yes. For maximum compatibility you should use WooCommerce version 3.6 or later. - ### Does this plugin work if my site is using a two-factor authentication plugin? Yes, mostly. @@ -125,18 +125,33 @@ A user needs the `edit_users` capability in order to switch user accounts. By de Yes. The `switch_users` meta capability can be explicitly granted to a user or a role to allow them to switch users regardless of whether or not they have the `edit_users` capability. For practical purposes, the user or role will also need the `list_users` capability so they can access the Users menu in the WordPress admin area. +~~~php +add_filter( 'user_has_cap', function( $allcaps, $caps, $args, $user ) { + if ( 'switch_to_user' === $args[0] ) { + if ( my_condition( $user ) ) { + $allcaps['switch_users'] = true; + } + } + return $allcaps; +}, 9, 4 ); +~~~ + +Note that this needs to happen before User Switching's own capability filtering, hence the priority of `9`. + ### Can the ability to switch accounts be denied from users? Yes. User capabilities in WordPress can be set to `false` to deny them from a user. Denying the `switch_users` capability prevents the user from switching users, even if they have the `edit_users` capability. - add_filter( 'user_has_cap', function( $allcaps, $caps, $args, $user ) { - if ( 'switch_to_user' === $args[0] ) { - if ( my_condition() ) { - $allcaps['switch_users'] = false; - } - } - return $allcaps; - }, 9, 4 ); +~~~php +add_filter( 'user_has_cap', function( $allcaps, $caps, $args, $user ) { + if ( 'switch_to_user' === $args[0] ) { + if ( my_condition( $user ) ) { + $allcaps['switch_users'] = false; + } + } + return $allcaps; +}, 9, 4 ); +~~~ Note that this needs to happen before User Switching's own capability filtering, hence the priority of `9`. @@ -144,47 +159,53 @@ Note that this needs to happen before User Switching's own capability filtering, Yes. Use the `user_switching::maybe_switch_url()` method for this. It takes care of authentication and returns a nonce-protected URL for the current user to switch into the provided user account. - if ( method_exists( 'user_switching', 'maybe_switch_url' ) ) { - $url = user_switching::maybe_switch_url( $target_user ); - if ( $url ) { - printf( - 'Switch to %2$s', - esc_url( $url ), - esc_html( $target_user->display_name ) - ); - } - } +~~~php +if ( method_exists( 'user_switching', 'maybe_switch_url' ) ) { + $url = user_switching::maybe_switch_url( $target_user ); + if ( $url ) { + printf( + 'Switch to %2$s', + esc_url( $url ), + esc_html( $target_user->display_name ) + ); + } +} +~~~ This link also works for switching back to the original user, but if you want an explicit link for this you can use the following code: - if ( method_exists( 'user_switching', 'get_old_user' ) ) { - $old_user = user_switching::get_old_user(); - if ( $old_user ) { - printf( - 'Switch back to %2$s', - esc_url( user_switching::switch_back_url( $old_user ) ), - esc_html( $old_user->display_name ) - ); - } - } +~~~php +if ( method_exists( 'user_switching', 'get_old_user' ) ) { + $old_user = user_switching::get_old_user(); + if ( $old_user ) { + printf( + 'Switch back to %2$s', + esc_url( user_switching::switch_back_url( $old_user ) ), + esc_html( $old_user->display_name ) + ); + } +} +~~~ ### Can I determine whether the current user switched into their account? -Yes. Use the `current_user_switched()` function for this. +Yes. Use the `current_user_switched()` function for this. If the current user switched into their account from another then it returns a `WP_User` object for their originating user, otherwise it returns false. - if ( function_exists( 'current_user_switched' ) ) { - $switched_user = current_user_switched(); - if ( $switched_user ) { - // User is logged in and has switched into their account. - // $switched_user is the WP_User object for their originating user. - } - } +~~~php +if ( function_exists( 'current_user_switched' ) ) { + $switched_user = current_user_switched(); + if ( $switched_user ) { + // User is logged in and has switched into their account. + // $switched_user is the WP_User object for their originating user. + } +} +~~~ ### Does this plugin allow a user to frame another user for an action? Potentially yes, but User Switching includes some safety protections for this and there are further precautions you can take as a site administrator: -* User Switching stores the ID of the originating user in the new session for the user they switch to. Although this session does not persist by default when they subsequently switch back, there will be a record of this ID if your MySQL server has query logging enabled. +* User Switching stores the ID of the originating user in the new WordPress user session for the user they switch to. Although this session does not persist by default when they subsequently switch back, there will be a record of this ID if your database server has query logging enabled. * User Switching stores the login name of the originating user in an authentication cookie (see the Privacy Statement for more information). If your server access logs store cookie data, there will be a record of this login name (along with the IP address) for each access request. * You can install an audit trail plugin such as Simple History, WP Activity Log, or Stream, all of which have built-in support for User Switching and all of which log an entry when a user switches into another account. * User Switching triggers an action when a user switches account, switches off, or switches back (see below). You can use these actions to perform additional logging for safety purposes depending on your requirements. @@ -205,49 +226,71 @@ Yes, there's a third party add-on plugin for this: [Admin Bar User Switching](ht Yes. When a user switches to another account, the `switch_to_user` hook is called: - /** - * Fires when a user switches to another user account. - * - * @since 0.6.0 - * @since 1.4.0 The `$new_token` and `$old_token` parameters were added. - * - * @param int $user_id The ID of the user being switched to. - * @param int $old_user_id The ID of the user being switched from. - * @param string $new_token The token of the session of the user being switched to. Can be an empty string - * or a token for a session that may or may not still be valid. - * @param string $old_token The token of the session of the user being switched from. - */ - do_action( 'switch_to_user', $user_id, $old_user_id, $new_token, $old_token ); +~~~php +/** + * Fires when a user switches to another user account. + * + * @since 0.6.0 + * @since 1.4.0 The `$new_token` and `$old_token` parameters were added. + * + * @param int $user_id The ID of the user being switched to. + * @param int $old_user_id The ID of the user being switched from. + * @param string $new_token The token of the session of the user being switched to. Can be an empty string + * or a token for a session that may or may not still be valid. + * @param string $old_token The token of the session of the user being switched from. + */ +do_action( 'switch_to_user', $user_id, $old_user_id, $new_token, $old_token ); +~~~ When a user switches back to their originating account, the `switch_back_user` hook is called: - /** - * Fires when a user switches back to their originating account. - * - * @since 0.6.0 - * @since 1.4.0 The `$new_token` and `$old_token` parameters were added. - * - * @param int $user_id The ID of the user being switched back to. - * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back - * after having been switched off. - * @param string $new_token The token of the session of the user being switched to. Can be an empty string - * or a token for a session that may or may not still be valid. - * @param string $old_token The token of the session of the user being switched from. - */ - do_action( 'switch_back_user', $user_id, $old_user_id, $new_token, $old_token ); +~~~php +/** + * Fires when a user switches back to their originating account. + * + * @since 0.6.0 + * @since 1.4.0 The `$new_token` and `$old_token` parameters were added. + * + * @param int $user_id The ID of the user being switched back to. + * @param int|false $old_user_id The ID of the user being switched from, or false if the user is switching back + * after having been switched off. + * @param string $new_token The token of the session of the user being switched to. Can be an empty string + * or a token for a session that may or may not still be valid. + * @param string $old_token The token of the session of the user being switched from. + */ +do_action( 'switch_back_user', $user_id, $old_user_id, $new_token, $old_token ); +~~~ When a user switches off, the `switch_off_user` hook is called: - /** - * Fires when a user switches off. - * - * @since 0.6.0 - * @since 1.4.0 The `$old_token` parameter was added. - * - * @param int $old_user_id The ID of the user switching off. - * @param string $old_token The token of the session of the user switching off. - */ - do_action( 'switch_off_user', $old_user_id, $old_token ); +~~~php +/** + * Fires when a user switches off. + * + * @since 0.6.0 + * @since 1.4.0 The `$old_token` parameter was added. + * + * @param int $old_user_id The ID of the user switching off. + * @param string $old_token The token of the session of the user switching off. + */ +do_action( 'switch_off_user', $old_user_id, $old_token ); +~~~ + +When a user switches to another account, switches off, or switches back, the `user_switching_redirect_to` filter is applied to the location that they get redirected to: + +~~~php +/** + * Filters the redirect location after a user switches to another account or switches off. + * + * @since 1.7.0 + * + * @param string $redirect_to The target redirect location, or an empty string if none is specified. + * @param string|null $redirect_type The redirect type, see the `user_switching::REDIRECT_*` constants. + * @param WP_User|null $new_user The user being switched to, or null if there is none. + * @param WP_User|null $old_user The user being switched from, or null if there is none. + */ +return apply_filters( 'user_switching_redirect_to', $redirect_to, $redirect_type, $new_user, $old_user ); +~~~ In addition, User Switching respects the following filters from WordPress core when appropriate: @@ -256,6 +299,6 @@ In addition, User Switching respects the following filters from WordPress core w ### Do you accept donations? -[I am accepting sponsorships via the GitHub Sponsors program](https://johnblackbourn.com/donations/) and any support you can give will help me maintain this plugin and keep it free for everyone. +[I am accepting sponsorships via the GitHub Sponsors program](https://github.com/sponsors/johnbillion) and any support you can give will help me maintain this plugin and keep it free for everyone. diff --git a/tests/.env.dist b/tests/.env.dist deleted file mode 100644 index 0c521df..0000000 --- a/tests/.env.dist +++ /dev/null @@ -1,4 +0,0 @@ -WP_TESTS_DB_NAME="wordpress_test" -WP_TESTS_DB_USER="root" -WP_TESTS_DB_PASS="" -WP_TESTS_DB_HOST="localhost" diff --git a/tests/_data/.gitkeep b/tests/_data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/_output/.gitkeep b/tests/_output/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/_support/AcceptanceTester.php b/tests/_support/AcceptanceTester.php new file mode 100644 index 0000000..3889308 --- /dev/null +++ b/tests/_support/AcceptanceTester.php @@ -0,0 +1,175 @@ +grabTablePrefix() + ); + $value = [ + 'core/edit-post' => [ + 'fullscreenMode' => false, + 'welcomeGuide' => false, + ], + ]; + + $this->haveUserMetaInDatabase( + 1, + $key, + [ + $value, + ] + ); + } + + /** + * Switch to the specified user + * + * @param string $user_login + */ + public function switchToUser( $user_login ) { + $user_id = $this->grabUserIdFromDatabase( $user_login ); + + $this->amOnAdminPage( sprintf( 'user-edit.php?user_id=%d', $user_id ) ); + $this->click( '#user_switching_switcher' ); + } + + /** + * Switch off + */ + public function switchOff() { + $this->moveMouseOver( '#wp-admin-bar-my-account' ); + $this->click( 'Switch Off' ); + } + + /** + * Switch back to the original user + * + * @param string $user_login + */ + public function switchBackTo( $user_login ) { + $display_name = $this->grabFromDatabase( + $this->grabUsersTableName(), + 'display_name', + [ + 'user_login' => $user_login, + ] + ); + + try { + $this->moveMouseOver( '#wp-admin-bar-my-account' ); + } catch ( \Codeception\Exception\ElementNotFound $e ) { + // Nothing. + } + + $this->click( sprintf( + 'Switch back to %s', + $display_name + ) ); + } + + /** + * Verify that the user is logged in as the specified user + * + * @param string $user_login + */ + public function amLoggedInAs( $user_login ) { + $display_name = $this->grabFromDatabase( + $this->grabUsersTableName(), + 'display_name', + [ + 'user_login' => $user_login, + ] + ); + + $this->see( + $display_name, + '#wpadminbar .display-name' + ); + } + + /** + * Verify that the user is logged out + */ + public function amLoggedOut() { + $this->cantSeeElement( '#wpadminbar .display-name' ); + } + + /** + * Verify the page language + * + * @param string $lang + */ + public function canSeeThePageInLanguage( $lang ) { + $this->canSeeTheElementInLanguage( 'html', $lang ); + } + + /** + * Verify the language of an element + * + * @param string $selector + * @param string $lang + */ + public function canSeeTheElementInLanguage( $selector, $lang ) { + $this->seeElement( $selector, [ + 'lang' => $lang, + ] ); + } + + /** + * Checks that the current page contains an admin success notice. + * + * @param string $text The message text to search for. + */ + public function seeAdminSuccessNotice( $text ) { + return $this->see( $text, '.notice-success' ); + } + + /** + * Checks that the current page contains an admin success notice. + * + * @param string $text The message text to search for. + */ + public function seeAdminWarningNotice( $text ) { + return $this->see( $text, '.notice-warning' ); + } + + /** + * Checks that the current page contains an admin success notice. + * + * @param string $text The message text to search for. + */ + public function seeAdminErrorNotice( $text ) { + return $this->see( $text, '.notice-error' ); + } + + /** + * Checks that the current page contains an admin success notice. + * + * @param string $text The message text to search for. + */ + public function seeAdminInfoNotice( $text ) { + return $this->see( $text, '.notice-info' ); + } +} diff --git a/tests/_support/WpunitTester.php b/tests/_support/WpunitTester.php new file mode 100644 index 0000000..ede0a7e --- /dev/null +++ b/tests/_support/WpunitTester.php @@ -0,0 +1,22 @@ +comment( 'As an administrator of a site which uses more than one language' ); + $I->comment( 'I need to be able to switch to user accounts that use a different language' ); + $I->comment( 'And see the output of User Switching in my original language' ); + + $I->haveUserInDatabase( 'autore', 'author', [ + 'display_name' => 'Autore', + 'meta' => [ + 'locale' => 'it_IT', + ], + ] ); + } + + public function SwitchFromEnglishAdminToItalianAuthorAndBack( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $I->switchToUser( 'autore' ); + $I->canSeeThePageInLanguage( 'it-IT' ); + $I->seeAdminSuccessNotice( 'Switched to Autore.' ); + $I->canSeeTheElementInLanguage( '#user_switching p', 'en-US' ); + + $I->amOnAdminPage( '/' ); + $I->switchBackTo( 'admin' ); + $I->canSeeThePageInLanguage( 'en-US' ); + $I->seeAdminSuccessNotice( 'Switched back to admin.' ); + } +} diff --git a/tests/acceptance/SwitchOffCest.php b/tests/acceptance/SwitchOffCest.php new file mode 100644 index 0000000..fa2bcac --- /dev/null +++ b/tests/acceptance/SwitchOffCest.php @@ -0,0 +1,116 @@ +comment( 'As an administrator' ); + $I->comment( 'I need to be able to switch off' ); + $I->comment( 'In order to view the site without logging out completely' ); + } + + public function SwitchOffFromDashboardAndBackFromFrontEnd( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $I->amOnAdminPage( '/' ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/?switched_off=true' ); + $I->amLoggedOut(); + + $I->switchBackTo( 'admin' ); + $I->seeCurrentUrlEquals( '/?user_switched=true&switched_back=true' ); + $I->amLoggedInAs( 'admin' ); + } + + public function SwitchOffFromDashboardAndBackFromLoginScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $I->amOnAdminPage( '/' ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/?switched_off=true' ); + $I->amLoggedOut(); + + $I->amOnPage( 'wp-login.php' ); + $I->switchBackTo( 'admin' ); + $I->seeCurrentUrlEquals( '/wp-admin/users.php' ); + $I->seeAdminSuccessNotice( 'Switched back to admin.' ); + $I->amLoggedInAs( 'admin' ); + } + + public function SwitchOffFromPublishedPostEditingScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $id = $I->havePostInDatabase( [ + 'post_status' => 'publish', + 'post_name' => 'hello-world', + ] ); + $I->amNotUsingTheEditorForTheFirstTime(); + $I->amEditingPostWithId( $id ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/hello-world/?switched_off=true' ); + $I->amLoggedOut(); + } + + public function SwitchOffFromDraftPostEditingScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $id = $I->havePostInDatabase( [ + 'post_status' => 'draft', + 'post_name' => 'hello-world', + ] ); + $I->amNotUsingTheEditorForTheFirstTime(); + $I->amEditingPostWithId( $id ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/?switched_off=true' ); + $I->amLoggedOut(); + } + + public function SwitchOffFromTermEditingScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $term = $I->haveTermInDatabase( 'hello', 'category' ); + $I->amOnAdminPage( '/term.php?taxonomy=category&tag_ID=' . $term[0] ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/category/hello/?switched_off=true' ); + $I->amLoggedOut(); + } + + public function SwitchOffFromUserEditingScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $id = $I->haveUserInDatabase( 'example', 'editor' ); + // https://github.com/lucatume/wp-browser/pull/586 + // $I->amEditingUserWithId( $id ); + $I->amOnAdminPage( '/user-edit.php?user_id=' . $id ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/author/example/?switched_off=true' ); + $I->amLoggedOut(); + } + + public function SwitchOffFromApprovedCommentEditingScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $postId = $I->havePostInDatabase( [ + 'post_status' => 'publish', + 'post_name' => 'leave-a-comment', + ] ); + $commentId = $I->haveCommentInDatabase( $postId, [ + 'comment_approved' => '1', + ] ); + $I->amOnAdminPage( '/comment.php?action=editcomment&c=' . $commentId ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/leave-a-comment/?switched_off=true#comment-' . $commentId ); + $I->amLoggedOut(); + } + + public function SwitchOffFromUnapprovedCommentEditingScreen( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $postId = $I->havePostInDatabase( [ + 'post_status' => 'publish', + 'post_name' => 'leave-a-comment', + ] ); + $commentId = $I->haveCommentInDatabase( $postId, [ + 'comment_approved' => '0', + ] ); + $I->amOnAdminPage( '/comment.php?action=editcomment&c=' . $commentId ); + $I->switchOff(); + $I->seeCurrentUrlEquals( '/leave-a-comment/?switched_off=true' ); + $I->amLoggedOut(); + } +} diff --git a/tests/acceptance/SwitchToEnglishCest.php b/tests/acceptance/SwitchToEnglishCest.php new file mode 100644 index 0000000..3db08e0 --- /dev/null +++ b/tests/acceptance/SwitchToEnglishCest.php @@ -0,0 +1,37 @@ +comment( 'As an administrator of a site which uses more than one language' ); + $I->comment( 'I need to be able to switch between users' ); + $I->comment( 'And see the output of User Switching in my original language' ); + + $I->haveUserInDatabase( 'admin_it', 'administrator', [ + 'display_name' => 'Admin IT', + 'meta' => [ + 'locale' => 'it_IT', + ], + ] ); + $I->haveUserInDatabase( 'author_en', 'author', [ + 'display_name' => 'Author EN', + ] ); + } + + public function SwitchFromItalianAdminToEnglishAuthorAndBack( \AcceptanceTester $I ): void { + $I->loginAs( 'admin_it', 'admin_it' ); + $I->switchToUser( 'author_en' ); + $I->canSeeThePageInLanguage( 'en-US' ); + $I->seeAdminSuccessNotice( 'Cambiato a Author EN.' ); + $I->canSeeTheElementInLanguage( '#user_switching p', 'it-IT' ); + + $I->amOnAdminPage( '/' ); + $I->switchBackTo( 'admin_it' ); + $I->canSeeThePageInLanguage( 'it-IT' ); + $I->seeAdminSuccessNotice( 'Tornato a Admin IT.' ); + } +} diff --git a/tests/acceptance/SwitchUserCest.php b/tests/acceptance/SwitchUserCest.php new file mode 100644 index 0000000..ecd39ce --- /dev/null +++ b/tests/acceptance/SwitchUserCest.php @@ -0,0 +1,45 @@ +comment( 'As an administrator' ); + $I->comment( 'I need to be able to switch between users' ); + $I->comment( 'In order to access different user accounts' ); + } + + public function SwitchToEditorThenBackFromFrontEnd( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $I->haveUserInDatabase( 'editor', 'editor' ); + + $I->switchToUser( 'editor' ); + $I->seeCurrentUrlEquals( '/wp-admin/' ); + $I->seeAdminSuccessNotice( 'Switched to editor.' ); + $I->amLoggedInAs( 'editor' ); + + $I->amOnPage( '/' ); + $I->switchBackTo( 'admin' ); + $I->seeCurrentUrlEquals( '/?user_switched=true&switched_back=true' ); + $I->amLoggedInAs( 'admin' ); + } + + public function SwitchToEditorThenBackFromAdminArea( \AcceptanceTester $I ): void { + $I->loginAsAdmin(); + $I->haveUserInDatabase( 'editor', 'editor' ); + + $I->switchToUser( 'editor' ); + $I->seeCurrentUrlEquals( '/wp-admin/' ); + $I->seeAdminSuccessNotice( 'Switched to editor.' ); + $I->amLoggedInAs( 'editor' ); + + $I->amOnAdminPage( 'tools.php' ); + $I->switchBackTo( 'admin' ); + $I->seeCurrentUrlEquals( '/wp-admin/tools.php' ); + $I->seeAdminSuccessNotice( 'Switched back to admin.' ); + $I->amLoggedInAs( 'admin' ); + } +} diff --git a/tests/cache/.gitkeep b/tests/cache/.gitkeep new file mode 100644 index 0000000..127395c --- /dev/null +++ b/tests/cache/.gitkeep @@ -0,0 +1 @@ +Important: This directory cannot be empty. diff --git a/tests/includes/bootstrap.php b/tests/includes/bootstrap.php deleted file mode 100644 index a8ce369..0000000 --- a/tests/includes/bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -ID, $expiry, 'auth' ); @@ -16,14 +17,14 @@ public function testValidCookiePassesAuthentication() : void { self::assertFalse( user_switching::authenticate_old_user( self::$testers['admin'] ) ); } - public function testExpiredCookieDoesNotPassAuthentication() : void { + public function testExpiredCookieDoesNotPassAuthentication(): void { $auth_cookie = wp_generate_auth_cookie( self::$testers['editor']->ID, time() - 1000, 'auth' ); $_COOKIE[ USER_SWITCHING_COOKIE ] = json_encode( array( $auth_cookie ) ); self::assertFalse( user_switching::authenticate_old_user( self::$testers['editor'] ) ); self::assertFalse( user_switching::authenticate_old_user( self::$testers['admin'] ) ); } - public function testValidCookieWithIncorrectSchemeDoesNotPassAuthentication() : void { + public function testValidCookieWithIncorrectSchemeDoesNotPassAuthentication(): void { $expiry = time() + 172800; $logged_in_cookie = wp_generate_auth_cookie( self::$testers['editor']->ID, $expiry, 'logged_in' ); @@ -36,16 +37,13 @@ public function testValidCookieWithIncorrectSchemeDoesNotPassAuthentication() : self::assertFalse( user_switching::authenticate_old_user( self::$testers['admin'] ) ); } - public function testMalformedCookieDoesNotPassAuthentication() : void { + public function testMalformedCookieDoesNotPassAuthentication(): void { $_COOKIE[ USER_SWITCHING_COOKIE ] = 'hello'; self::assertFalse( user_switching::authenticate_old_user( self::$testers['editor'] ) ); self::assertFalse( user_switching::authenticate_old_user( self::$testers['admin'] ) ); } - /** - * @testdox A non-JSON encoded cookie does not pass authentication - */ - public function testANonJsonEncodedCookieDoesNotPassAuthentication() : void { + public function testANonJsonEncodedCookieDoesNotPassAuthentication(): void { $expiry = time() + 172800; $auth_cookie = wp_generate_auth_cookie( self::$testers['editor']->ID, $expiry, 'auth' ); @@ -54,10 +52,9 @@ public function testANonJsonEncodedCookieDoesNotPassAuthentication() : void { self::assertFalse( user_switching::authenticate_old_user( self::$testers['admin'] ) ); } - public function testNoCookieDoesNotPassAuthentication() : void { + public function testNoCookieDoesNotPassAuthentication(): void { unset( $_COOKIE[ USER_SWITCHING_COOKIE ] ); self::assertFalse( user_switching::authenticate_old_user( self::$testers['editor'] ) ); self::assertFalse( user_switching::authenticate_old_user( self::$testers['admin'] ) ); } - } diff --git a/tests/test-caps.php b/tests/integration/CapabilitiesTest.php similarity index 82% rename from tests/test-caps.php rename to tests/integration/CapabilitiesTest.php index cb35754..edd0316 100644 --- a/tests/test-caps.php +++ b/tests/integration/CapabilitiesTest.php @@ -1,46 +1,45 @@ -> + * @return array> */ - public function data_roles() : array { + public function data_roles(): array { $roles = [ - [ + 'admin' => [ 'admin', ! is_multisite(), ], - [ + 'editor' => [ 'editor', false, ], - [ + 'author' => [ 'author', false, ], - [ + 'contributor' => [ 'contributor', false, ], - [ + 'subscriber' => [ 'subscriber', false, ], - [ + 'none' => [ 'no_role', false, ], ]; if ( is_multisite() ) { - $roles[] = [ + $roles['super admin'] = [ 'super', true, ]; @@ -49,7 +48,7 @@ public function data_roles() : array { return $roles; } - public function testAllRolesAreTested() : void { + public function testAllRolesAreTested(): void { $tested_roles = array_column( $this->data_roles(), 0 ); self::assertSame( array_keys( self::$testers ), $tested_roles ); @@ -58,9 +57,8 @@ public function testAllRolesAreTested() : void { /** * @dataProvider data_roles - * @testdox User with role of $role can or cannot switch according to role */ - public function testUserCanOrCannotSwitchAccordingToRole( string $role, bool $can_switch ) : void { + public function testUserCanOrCannotSwitchAccordingToRole( string $role, bool $can_switch ): void { foreach ( self::$users as $user_role => $user ) { if ( self::$testers[ $role ]->ID === $user->ID ) { # No user can switch to themselves: @@ -78,7 +76,7 @@ public function testUserCanOrCannotSwitchAccordingToRole( string $role, bool $ca self::assertSame( $can_switch, user_can( self::$testers[ $role ]->ID, 'switch_off' ) ); } - public function testAbilityToSwitchUsersCanBeGrantedToUser() : void { + public function testAbilityToSwitchUsersCanBeGrantedToUser(): void { # Editors cannot switch to other users: $can_already_switch = user_can( self::$testers['editor']->ID, 'switch_to_user', self::$users['admin']->ID ); @@ -87,7 +85,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToUser() : void { # Ensure the user can switch: $can_switch_user = user_can( self::$testers['editor']->ID, 'switch_to_user', self::$users['admin']->ID ); - $can_switch_off = user_can( self::$testers['editor']->ID, 'switch_off' ); + $can_switch_off = user_can( self::$testers['editor']->ID, 'switch_off' ); # Revert the cap: self::$testers['editor']->remove_cap( 'switch_users' ); @@ -98,7 +96,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToUser() : void { self::assertTrue( $can_switch_off ); } - public function testAbilityToSwitchUsersCanBeGrantedToRole() : void { + public function testAbilityToSwitchUsersCanBeGrantedToRole(): void { # Editors cannot switch to other users: $can_already_switch = user_can( self::$testers['editor']->ID, 'switch_to_user', self::$users['admin']->ID ); @@ -110,7 +108,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToRole() : void { # Ensure the user can switch: $can_switch_user = user_can( self::$testers['editor']->ID, 'switch_to_user', self::$users['admin']->ID ); - $can_switch_off = user_can( self::$testers['editor']->ID, 'switch_off' ); + $can_switch_off = user_can( self::$testers['editor']->ID, 'switch_off' ); # Revert the cap: $role->remove_cap( 'switch_users' ); @@ -124,7 +122,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToRole() : void { /** * @group ms-excluded */ - public function testAbilityToSwitchUsersCanBeDeniedFromUser() : void { + public function testAbilityToSwitchUsersCanBeDeniedFromUser(): void { # Admins can switch to other users: $can_already_switch = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); @@ -133,7 +131,7 @@ public function testAbilityToSwitchUsersCanBeDeniedFromUser() : void { # Ensure the user can no longer switch: $can_switch_user = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); - $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); + $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); # Revert the cap: self::$testers['admin']->remove_cap( 'switch_users' ); @@ -147,7 +145,7 @@ public function testAbilityToSwitchUsersCanBeDeniedFromUser() : void { /** * @group ms-excluded */ - public function testAbilityToSwitchUsersCanBeDeniedFromRole() : void { + public function testAbilityToSwitchUsersCanBeDeniedFromRole(): void { # Admins can switch to other users: $can_already_switch = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); @@ -159,7 +157,7 @@ public function testAbilityToSwitchUsersCanBeDeniedFromRole() : void { # Ensure the user can no longer switch: $can_switch_user = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); - $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); + $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); # Revert the cap: $role->remove_cap( 'switch_users' ); @@ -174,7 +172,7 @@ public function testAbilityToSwitchUsersCanBeDeniedFromRole() : void { * @group multisite * @group ms-required */ - public function testAbilityToSwitchUsersCanBeGrantedToAdministratorRoleOnMultisite() : void { + public function testAbilityToSwitchUsersCanBeGrantedToAdministratorRoleOnMultisite(): void { # Admins on Multisite cannot switch to other users: $can_already_switch = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); @@ -186,7 +184,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToAdministratorRoleOnMultisi # Ensure the user can switch: $can_switch_user = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); - $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); + $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); # Revert the cap: $role->remove_cap( 'switch_users' ); @@ -201,7 +199,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToAdministratorRoleOnMultisi * @group multisite * @group ms-required */ - public function testAbilityToSwitchUsersCanBeGrantedToAdministratorUserOnMultisite() : void { + public function testAbilityToSwitchUsersCanBeGrantedToAdministratorUserOnMultisite(): void { # Admins on Multisite cannot switch to other users: $can_already_switch = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); @@ -210,7 +208,7 @@ public function testAbilityToSwitchUsersCanBeGrantedToAdministratorUserOnMultisi # Ensure the user can switch: $can_switch_user = user_can( self::$testers['admin']->ID, 'switch_to_user', self::$users['author']->ID ); - $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); + $can_switch_off = user_can( self::$testers['admin']->ID, 'switch_off' ); # Revert the cap: self::$testers['admin']->remove_cap( 'switch_users' ); @@ -223,10 +221,8 @@ public function testAbilityToSwitchUsersCanBeGrantedToAdministratorUserOnMultisi /** * @dataProvider data_roles - * @testdox User with role of $role cannot switch to no user */ - public function testSwitchingToNoUserIsNotAllowed( string $role ) : void { + public function testSwitchingToNoUserIsNotAllowed( string $role ): void { self::assertFalse( user_can( self::$testers[ $role ]->ID, 'switch_to_user', 0 ) ); } - } diff --git a/tests/test-plugin.php b/tests/integration/PluginTest.php similarity index 62% rename from tests/test-plugin.php rename to tests/integration/PluginTest.php index c8f9677..55a03df 100644 --- a/tests/test-plugin.php +++ b/tests/integration/PluginTest.php @@ -1,30 +1,30 @@ - + * @var ?array */ private $readme_data; - public function testStableTagMatchesVersion() : void { + public function testStableTagMatchesVersion(): void { $readme_data = $this->get_readme(); if ( null === $readme_data ) { self::fail( 'There is no readme file' ); } - $plugin_data = get_plugin_data( dirname( dirname( __FILE__ ) ) . '/user-switching.php' ); + $plugin_data = get_plugin_data( dirname( dirname( __DIR__ ) ) . '/user-switching.php' ); self::assertEquals( $readme_data['stable_tag'], $plugin_data['Version'] ); } /** - * @return array + * @return ?array */ - private function get_readme() :? array { + private function get_readme(): ?array { if ( ! isset( $this->readme_data ) ) { - $file = dirname( dirname( __FILE__ ) ) . '/readme.md'; + $file = dirname( dirname( __DIR__ ) ) . '/readme.md'; if ( ! is_file( $file ) ) { return null; @@ -47,5 +47,4 @@ private function get_readme() :? array { return $this->readme_data; } - } diff --git a/tests/test-sessions.php b/tests/integration/SessionsTest.php similarity index 87% rename from tests/test-sessions.php rename to tests/integration/SessionsTest.php index e7b4f67..8f21555 100644 --- a/tests/test-sessions.php +++ b/tests/integration/SessionsTest.php @@ -1,13 +1,15 @@ -ID ); - $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); - $admin_before = $admin_manager->get_all(); + $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); + $admin_before = $admin_manager->get_all(); // Set up the author session manager, but with no session $author_manager = WP_Session_Tokens::get_instance( self::$users['author']->ID ); - $author_before = $author_manager->get_all(); + $author_before = $author_manager->get_all(); // Set up the admin user state wp_set_current_user( $admin->ID ); @@ -44,7 +46,7 @@ public function testExtraSessionsAreNotCreatedForUsersWhenSwitching() : void { /** * @covers \switch_off_user */ - public function testExtraSessionsAreNotCreatedForUserWhenSwitchingOff() : void { + public function testExtraSessionsAreNotCreatedForUserWhenSwitchingOff(): void { if ( is_multisite() ) { $admin = self::$testers['super']; } else { @@ -53,8 +55,8 @@ public function testExtraSessionsAreNotCreatedForUserWhenSwitchingOff() : void { // Set up the admin session manager with a session $admin_manager = WP_Session_Tokens::get_instance( $admin->ID ); - $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); - $admin_before = $admin_manager->get_all(); + $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); + $admin_before = $admin_manager->get_all(); // Set up the admin user state wp_set_current_user( $admin->ID ); @@ -74,7 +76,7 @@ public function testExtraSessionsAreNotCreatedForUserWhenSwitchingOff() : void { * @covers \switch_to_user * @covers \switch_off_user */ - public function testPreviousSessionForUserIsReusedWhenSwitchingBack() : void { + public function testPreviousSessionForUserIsReusedWhenSwitchingBack(): void { if ( is_multisite() ) { $admin = self::$testers['super']; } else { @@ -83,8 +85,8 @@ public function testPreviousSessionForUserIsReusedWhenSwitchingBack() : void { // Set up the admin session manager with a session $admin_manager = WP_Session_Tokens::get_instance( $admin->ID ); - $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); - $admin_before = $admin_manager->get_all(); + $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); + $admin_before = $admin_manager->get_all(); // Set up the author session manager, but with no session $author_manager = WP_Session_Tokens::get_instance( self::$users['author']->ID ); @@ -127,7 +129,7 @@ public function testPreviousSessionForUserIsReusedWhenSwitchingBack() : void { /** * @covers \switch_to_user */ - public function testExpiredSessionPreventsUserFromSwitchingBack() : void { + public function testExpiredSessionPreventsUserFromSwitchingBack(): void { if ( is_multisite() ) { $admin = self::$testers['super']; } else { @@ -136,12 +138,12 @@ public function testExpiredSessionPreventsUserFromSwitchingBack() : void { // Set up the admin session manager with a session $admin_manager = WP_Session_Tokens::get_instance( $admin->ID ); - $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); - $admin_before = $admin_manager->get_all(); + $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); + $admin_before = $admin_manager->get_all(); // Set up the author session manager, but with no session $author_manager = WP_Session_Tokens::get_instance( self::$users['author']->ID ); - $author_before = $author_manager->get_all(); + $author_before = $author_manager->get_all(); // Set up the admin user state wp_set_current_user( $admin->ID ); @@ -183,7 +185,7 @@ public function testExpiredSessionPreventsUserFromSwitchingBack() : void { * @covers \switch_to_user * @covers \switch_off_user */ - public function testSessionTokensAreCorrectlyReusedWhenSwitching() : void { + public function testSessionTokensAreCorrectlyReusedWhenSwitching(): void { if ( is_multisite() ) { $admin = self::$testers['super']; } else { @@ -192,7 +194,7 @@ public function testSessionTokensAreCorrectlyReusedWhenSwitching() : void { // Set up the admin session manager with a session $admin_manager = WP_Session_Tokens::get_instance( $admin->ID ); - $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); + $admin_token = $admin_manager->create( time() + DAY_IN_SECONDS ); // Set up the author session manager, but with no session $author_manager = WP_Session_Tokens::get_instance( self::$users['author']->ID ); @@ -202,10 +204,10 @@ public function testSessionTokensAreCorrectlyReusedWhenSwitching() : void { wp_set_auth_cookie( $admin->ID, false, '', $admin_token ); // Switch user - $user = switch_to_user( self::$users['author']->ID ); + $user = switch_to_user( self::$users['author']->ID ); $author_token = wp_get_session_token(); - $cookies = user_switching_get_auth_cookie(); - $cookie = end( $cookies ); + $cookies = user_switching_get_auth_cookie(); + $cookie = end( $cookies ); self::assertIsString( $cookie ); @@ -236,9 +238,9 @@ public function testSessionTokensAreCorrectlyReusedWhenSwitching() : void { self::assertNull( $author_manager->get( $author_token ) ); // Switch off - $off = switch_off_user(); + $off = switch_off_user(); $cookies = user_switching_get_auth_cookie(); - $cookie = end( $cookies ); + $cookie = end( $cookies ); self::assertIsString( $cookie ); @@ -256,5 +258,4 @@ public function testSessionTokensAreCorrectlyReusedWhenSwitching() : void { self::assertCount( 1, $admin_manager->get_all() ); self::assertNotNull( $admin_manager->get( $admin_token ) ); } - } diff --git a/tests/test-switching.php b/tests/integration/SwitchingTest.php similarity index 88% rename from tests/test-switching.php rename to tests/integration/SwitchingTest.php index f3a8756..4c7e016 100644 --- a/tests/test-switching.php +++ b/tests/integration/SwitchingTest.php @@ -1,31 +1,32 @@ -go_to( $url ); self::assertSame( user_switching::current_url(), $url ); @@ -217,20 +217,20 @@ public function testCurrentUrl() : void { * @param int $user_id * @param int|false $old_user_id */ - public function _action_switch_user( int $user_id, $old_user_id ) : void { - $this->test_switching_user_id = $user_id; + public function _action_switch_user( $user_id, $old_user_id ): void { + $this->test_switching_user_id = $user_id; $this->test_switching_old_user_id = $old_user_id; } - public function _action_switch_off( int $old_user_id ) : void { - $this->test_switching_user_id = false; + public function _action_switch_off( int $old_user_id ): void { + $this->test_switching_user_id = false; $this->test_switching_old_user_id = $old_user_id; } - public function _filter_auth_cookie_expiration( int $length, int $user_id, bool $remember ) : int { - $this->test_switching_auth_cookie_user_id = $user_id; + public function _filter_auth_cookie_expiration( int $length, int $user_id, bool $remember ): int { + $this->test_switching_auth_cookie_user_id = $user_id; $this->test_switching_auth_cookie_remember = $remember; + return $length; } - } diff --git a/tests/user-switching-test.php b/tests/integration/Test.php similarity index 54% rename from tests/user-switching-test.php rename to tests/integration/Test.php index 49b3f9e..60c0fa8 100644 --- a/tests/user-switching-test.php +++ b/tests/integration/Test.php @@ -1,35 +1,34 @@ - */ - protected static $users = array(); + protected static $users = []; /** - * @var WP_User[] + * @var array */ - protected static $testers = array(); + protected static $testers = []; /** * @var array */ - protected $sessions = array(); + protected $sessions = []; /** * @return void */ - public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + public static function wpSetUpBeforeClass( \WP_UnitTest_Factory $factory ) { $roles = array( - 'admin' => 'administrator', - 'editor' => 'editor', - 'author' => 'author', + 'admin' => 'administrator', + 'editor' => 'editor', + 'author' => 'author', 'contributor' => 'contributor', - 'subscriber' => 'subscriber', - 'no_role' => '', + 'subscriber' => 'subscriber', + 'no_role' => '', ); foreach ( $roles as $name => $role ) { @@ -55,50 +54,51 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { add_filter( 'user_switching_send_auth_cookies', '__return_false' ); } - /** - * @return void - */ - public function setUp() { - parent::setUp(); - + public function _before(): void { add_action( 'set_auth_cookie', array( $this, 'action_set_auth_cookie' ), 10, 6 ); - add_action( 'set_logged_in_cookie', array( $this, 'action_set_logged_in_cookie' ), 10, 6 ); + add_action( 'set_logged_in_cookie', array( $this, 'action_set_logged_in_cookie' ), 10 ); add_action( 'clear_auth_cookie', array( $this, 'action_clear_auth_cookie' ) ); - add_action( 'set_user_switching_cookie', array( $this, 'action_set_user_switching_cookie' ), 10, 5 ); - add_action( 'set_olduser_cookie', array( $this, 'action_set_olduser_cookie' ), 10, 5 ); + add_action( 'set_user_switching_cookie', array( $this, 'action_set_user_switching_cookie' ), 10 ); + add_action( 'set_olduser_cookie', array( $this, 'action_set_olduser_cookie' ), 10 ); add_action( 'clear_olduser_cookie', array( $this, 'action_clear_olduser_cookie' ) ); } - public function action_set_auth_cookie( string $cookie, int $expire, int $expiration, int $user_id, string $scheme, string $token ) : void { + final public function action_set_auth_cookie( + string $cookie, + int $expire, + int $expiration, + int $user_id, + string $scheme, + string $token + ): void { $_COOKIE[ SECURE_AUTH_COOKIE ] = $cookie; - $_COOKIE[ AUTH_COOKIE ] = $cookie; - $this->sessions[ $user_id ] = $token; + $_COOKIE[ AUTH_COOKIE ] = $cookie; + $this->sessions[ $user_id ] = $token; } - public function action_set_logged_in_cookie( string $cookie, int $expire, int $expiration, int $user_id, string $scheme, string $token ) : void { + final public function action_set_logged_in_cookie( string $cookie ): void { $_COOKIE[ LOGGED_IN_COOKIE ] = $cookie; } - public function action_clear_auth_cookie() : void { + final public function action_clear_auth_cookie(): void { unset( $_COOKIE[ LOGGED_IN_COOKIE ] ); unset( $_COOKIE[ SECURE_AUTH_COOKIE ] ); unset( $_COOKIE[ AUTH_COOKIE ] ); } - public function action_set_user_switching_cookie( string $cookie, int $expiration, int $user_id, string $scheme, string $token ) : void { - $_COOKIE[ USER_SWITCHING_COOKIE ] = $cookie; + final public function action_set_user_switching_cookie( string $cookie ): void { + $_COOKIE[ USER_SWITCHING_COOKIE ] = $cookie; $_COOKIE[ USER_SWITCHING_SECURE_COOKIE ] = $cookie; } - public function action_set_olduser_cookie( string $cookie, int $expiration, int $user_id, string $scheme, string $token ) : void { + final public function action_set_olduser_cookie( string $cookie ): void { $_COOKIE[ USER_SWITCHING_OLDUSER_COOKIE ] = $cookie; } - public function action_clear_olduser_cookie() : void { + final public function action_clear_olduser_cookie(): void { unset( $_COOKIE[ USER_SWITCHING_COOKIE ] ); unset( $_COOKIE[ USER_SWITCHING_SECURE_COOKIE ] ); unset( $_COOKIE[ USER_SWITCHING_OLDUSER_COOKIE ] ); } - } diff --git a/tests/stubs.php b/tests/phpstan/stubs.php similarity index 78% rename from tests/stubs.php rename to tests/phpstan/stubs.php index 0ede72c..02c4188 100644 --- a/tests/stubs.php +++ b/tests/phpstan/stubs.php @@ -1,14 +1,5 @@ */ @@ -68,7 +58,6 @@ function bbp_get_user_id() {} function bbp_get_user_profile_url( int $user_id ) {} class WooCommerce { - /** * @var \WC_Session */ @@ -82,9 +71,19 @@ class WC_Session { public function forget_session() {} } +class WC_Order { + /** + * @return \WP_User|false + */ + public function get_user() {} + + /** + * @return string + */ + public function get_view_order_url() {} +} + /** * @return \WooCommerce */ function WC() {} - -class PHPUnit_Framework_TestCase {} diff --git a/tests/test-docs.php b/tests/test-docs.php deleted file mode 100644 index e7cbdae..0000000 --- a/tests/test-docs.php +++ /dev/null @@ -1,31 +0,0 @@ - - */ - protected function getTestFunctions() : array { - return array( - 'user_switching_set_olduser_cookie', - 'user_switching_clear_olduser_cookie', - 'user_switching_get_olduser_cookie', - 'user_switching_get_auth_cookie', - 'switch_to_user', - 'switch_off_user', - 'current_user_switched', - ); - } - - /** - * @return array - */ - protected function getTestClasses() : array { - return array( - 'user_switching', - ); - } - -} diff --git a/tests/wp-config.php b/tests/wp-config.php deleted file mode 100644 index f245ce8..0000000 --- a/tests/wp-config.php +++ /dev/null @@ -1,43 +0,0 @@ -load(); -} - -// Test with WordPress debug mode (default). -define( 'WP_DEBUG', true ); - -// Prevent WP-Cron doing its thing during testing. -define( 'DISABLE_WP_CRON', true ); - -define( 'WP_PLUGIN_DIR', dirname( dirname( __DIR__ ) ) ); - -// WARNING WARNING WARNING! -// These tests will DROP ALL TABLES in the database with the prefix named below. -// DO NOT use a production database or one that is shared with something else. -define( 'DB_NAME', getenv( 'WP_TESTS_DB_NAME' ) ?: 'wordpress_test' ); -define( 'DB_USER', getenv( 'WP_TESTS_DB_USER' ) ?: 'root' ); -define( 'DB_PASSWORD', getenv( 'WP_TESTS_DB_PASS' ) ?: '' ); -define( 'DB_HOST', getenv( 'WP_TESTS_DB_HOST' ) ?: 'localhost' ); -define( 'DB_CHARSET', 'utf8' ); -define( 'DB_COLLATE', '' ); - -/** - * WordPress Database Table prefix. - * - * You can have multiple installations in one database if you give each - * a unique prefix. Only numbers, letters, and underscores please! - */ -$table_prefix = 'wp_'; - -/** Sets up WordPress vars and included files. */ -require_once ABSPATH . 'wp-settings.php'; diff --git a/tests/wp-tests-config.php b/tests/wp-tests-config.php deleted file mode 100644 index 37194f7..0000000 --- a/tests/wp-tests-config.php +++ /dev/null @@ -1,40 +0,0 @@ -load(); -} - -// Path to the WordPress codebase to test. -define( 'ABSPATH', $root . '/' . $composer['extra']['wordpress-install-dir'] . '/' ); - -// Path to the theme to test with. -define( 'WP_DEFAULT_THEME', 'default' ); - -// Test with WordPress debug mode (default). -define( 'WP_DEBUG', true ); - -// WARNING WARNING WARNING! -// These tests will DROP ALL TABLES in the database with the prefix named below. -// DO NOT use a production database or one that is shared with something else. -define( 'DB_NAME', getenv( 'WP_TESTS_DB_NAME' ) ?: 'wordpress_test' ); -define( 'DB_USER', getenv( 'WP_TESTS_DB_USER' ) ?: 'root' ); -define( 'DB_PASSWORD', getenv( 'WP_TESTS_DB_PASS' ) ?: '' ); -define( 'DB_HOST', getenv( 'WP_TESTS_DB_HOST' ) ?: 'localhost' ); -define( 'DB_CHARSET', 'utf8' ); -define( 'DB_COLLATE', '' ); - -// Test suite configuration. -define( 'WP_TESTS_DOMAIN', 'example.org' ); -define( 'WP_TESTS_EMAIL', 'admin@example.org' ); -define( 'WP_TESTS_TITLE', 'User Switching Tests' ); -define( 'WP_PHP_BINARY', 'php' ); diff --git a/user-switching.php b/user-switching.php index cb61451..4aea718 100644 --- a/user-switching.php +++ b/user-switching.php @@ -4,20 +4,22 @@ * * @package user-switching * @link https://github.com/johnbillion/user-switching - * @author John Blackbourn - * @copyright 2009-2021 John Blackbourn + * @author John Blackbourn + * @copyright 2009-2023 John Blackbourn * @license GPL v2 or later * - * Plugin Name: User Switching - * Description: Instant switching between user accounts in WordPress - * Version: 1.5.8 - * Plugin URI: https://wordpress.org/plugins/user-switching/ - * Author: John Blackbourn & contributors - * Author URI: https://github.com/johnbillion/user-switching/graphs/contributors - * Text Domain: user-switching - * Domain Path: /languages/ - * Network: true - * Requires PHP: 5.3 + * Plugin Name: User Switching + * Description: Instant switching between user accounts in WordPress + * Version: 1.7.1 + * Plugin URI: https://wordpress.org/plugins/user-switching/ + * Author: John Blackbourn & contributors + * Author URI: https://github.com/johnbillion/user-switching/graphs/contributors + * Text Domain: user-switching + * Domain Path: /languages/ + * Network: true + * Requires at least: 5.6 + * Requires PHP: 7.4 + * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,11 +32,14 @@ * GNU General Public License for more details. */ +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + /** * Main singleton class for the User Switching plugin. */ class user_switching { - /** * The name used to identify the application during a WordPress redirect. * @@ -42,6 +47,13 @@ class user_switching { */ public static $application = 'WordPress/User Switching'; + const REDIRECT_TYPE_NONE = null; + const REDIRECT_TYPE_URL = 'url'; + const REDIRECT_TYPE_POST = 'post'; + const REDIRECT_TYPE_TERM = 'term'; + const REDIRECT_TYPE_USER = 'user'; + const REDIRECT_TYPE_COMMENT = 'comment'; + /** * Sets up all the filters and actions. * @@ -49,28 +61,33 @@ class user_switching { */ public function init_hooks() { // Required functionality: - add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 ); - add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 10, 4 ); - add_filter( 'user_row_actions', array( $this, 'filter_user_row_actions' ), 10, 2 ); - add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ), 1 ); - add_action( 'init', array( $this, 'action_init' ) ); - add_action( 'all_admin_notices', array( $this, 'action_admin_notices' ), 1 ); - add_action( 'wp_logout', 'user_switching_clear_olduser_cookie' ); - add_action( 'wp_login', 'user_switching_clear_olduser_cookie' ); + add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 ); + add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 10, 4 ); + add_filter( 'user_row_actions', array( $this, 'filter_user_row_actions' ), 10, 2 ); + add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ), 1 ); + add_action( 'init', array( $this, 'action_init' ) ); + add_action( 'all_admin_notices', array( $this, 'action_admin_notices' ), 1 ); + add_action( 'wp_logout', 'user_switching_clear_olduser_cookie' ); + add_action( 'wp_login', 'user_switching_clear_olduser_cookie' ); // Nice-to-haves: - add_filter( 'ms_user_row_actions', array( $this, 'filter_user_row_actions' ), 10, 2 ); - add_filter( 'login_message', array( $this, 'filter_login_message' ), 1 ); - add_filter( 'removable_query_args', array( $this, 'filter_removable_query_args' ) ); - add_action( 'wp_meta', array( $this, 'action_wp_meta' ) ); - add_action( 'wp_footer', array( $this, 'action_wp_footer' ) ); - add_action( 'personal_options', array( $this, 'action_personal_options' ) ); - add_action( 'admin_bar_menu', array( $this, 'action_admin_bar_menu' ), 11 ); - add_action( 'bp_member_header_actions', array( $this, 'action_bp_button' ), 11 ); - add_action( 'bp_directory_members_actions', array( $this, 'action_bp_button' ), 11 ); - add_action( 'bbp_template_after_user_details', array( $this, 'action_bbpress_button' ) ); - add_action( 'switch_to_user', array( $this, 'forget_woocommerce_session' ) ); - add_action( 'switch_back_user', array( $this, 'forget_woocommerce_session' ) ); + add_filter( 'ms_user_row_actions', array( $this, 'filter_user_row_actions' ), 10, 2 ); + add_filter( 'login_message', array( $this, 'filter_login_message' ), 1 ); + add_filter( 'removable_query_args', array( $this, 'filter_removable_query_args' ) ); + add_action( 'wp_meta', array( $this, 'action_wp_meta' ) ); + add_filter( 'plugin_row_meta', array( $this, 'filter_plugin_row_meta' ), 10, 2 ); + add_action( 'wp_footer', array( $this, 'action_wp_footer' ) ); + add_action( 'personal_options', array( $this, 'action_personal_options' ) ); + add_action( 'admin_bar_menu', array( $this, 'action_admin_bar_menu' ), 11 ); + add_action( 'bp_member_header_actions', array( $this, 'action_bp_button' ), 11 ); + add_action( 'bp_directory_members_actions', array( $this, 'action_bp_button' ), 11 ); + add_action( 'bbp_template_after_user_details_menu_items', array( $this, 'action_bbpress_button' ) ); + add_action( 'woocommerce_login_form_start', array( $this, 'action_woocommerce_login_form_start' ), 10, 0 ); + add_action( 'woocommerce_admin_order_data_after_order_details', array( $this, 'action_woocommerce_order_details' ), 1 ); + add_filter( 'woocommerce_account_menu_items', array( $this, 'filter_woocommerce_account_menu_items' ), 999 ); + add_filter( 'woocommerce_get_endpoint_url', array( $this, 'filter_woocommerce_get_endpoint_url' ), 10, 2 ); + add_action( 'switch_to_user', array( $this, 'forget_woocommerce_session' ) ); + add_action( 'switch_back_user', array( $this, 'forget_woocommerce_session' ) ); } /** @@ -110,8 +127,14 @@ public function action_personal_options( WP_User $user ) { ?> - - + + + + + + + + 'true', 'switched_back' => 'true', ); @@ -239,7 +258,7 @@ public function action_init() { // Check authentication: if ( ! $current_user || ! current_user_can( 'switch_off' ) ) { /* Translators: "switch off" means to temporarily log out */ - wp_die( esc_html__( 'Could not switch off.', 'user-switching' ) ); + wp_die( esc_html__( 'Could not switch off.', 'user-switching' ), 403 ); } // Check intent: @@ -248,7 +267,7 @@ public function action_init() { // Switch off: if ( switch_off_user() ) { $redirect_to = self::get_redirect( null, $current_user ); - $args = array( + $args = array( 'switched_off' => 'true', ); @@ -260,7 +279,7 @@ public function action_init() { exit; } else { /* Translators: "switch off" means to temporarily log out */ - wp_die( esc_html__( 'Could not switch off.', 'user-switching' ) ); + wp_die( esc_html__( 'Could not switch off.', 'user-switching' ), 403 ); } break; @@ -275,12 +294,76 @@ public function action_init() { * @return string The URL to redirect to. */ protected static function get_redirect( WP_User $new_user = null, WP_User $old_user = null ) { + $redirect_to = ''; + $requested_redirect_to = ''; + $redirect_type = self::REDIRECT_TYPE_NONE; + if ( ! empty( $_REQUEST['redirect_to'] ) ) { - $redirect_to = self::remove_query_args( wp_unslash( $_REQUEST['redirect_to'] ) ); + // URL + $redirect_to = self::remove_query_args( wp_unslash( $_REQUEST['redirect_to'] ) ); $requested_redirect_to = wp_unslash( $_REQUEST['redirect_to'] ); - } else { - $redirect_to = ''; - $requested_redirect_to = ''; + $redirect_type = self::REDIRECT_TYPE_URL; + } elseif ( ! empty( $_GET['redirect_to_post'] ) ) { + // Post + $post_id = absint( $_GET['redirect_to_post'] ); + $redirect_type = self::REDIRECT_TYPE_POST; + + if ( function_exists( 'is_post_publicly_viewable' ) && is_post_publicly_viewable( $post_id ) ) { + $link = get_permalink( $post_id ); + + if ( is_string( $link ) ) { + $redirect_to = $link; + $requested_redirect_to = $link; + } + } + } elseif ( ! empty( $_GET['redirect_to_term'] ) ) { + // Term + $term = get_term( absint( $_GET['redirect_to_term'] ) ); + $redirect_type = self::REDIRECT_TYPE_TERM; + + if ( ( $term instanceof WP_Term ) && is_taxonomy_viewable( $term->taxonomy ) ) { + $link = get_term_link( $term ); + + if ( is_string( $link ) ) { + $redirect_to = $link; + $requested_redirect_to = $link; + } + } + } elseif ( ! empty( $_GET['redirect_to_user'] ) ) { + // User + $user = get_userdata( absint( $_GET['redirect_to_user'] ) ); + $redirect_type = self::REDIRECT_TYPE_USER; + + if ( $user instanceof WP_User ) { + $link = get_author_posts_url( $user->ID ); + + if ( is_string( $link ) ) { + $redirect_to = $link; + $requested_redirect_to = $link; + } + } + } elseif ( ! empty( $_GET['redirect_to_comment'] ) ) { + // Comment + $comment = get_comment( absint( $_GET['redirect_to_comment'] ) ); + $redirect_type = self::REDIRECT_TYPE_COMMENT; + + if ( $comment instanceof WP_Comment ) { + if ( 'approved' === wp_get_comment_status( $comment ) ) { + $link = get_comment_link( $comment ); + + if ( is_string( $link ) ) { + $redirect_to = $link; + $requested_redirect_to = $link; + } + } elseif ( function_exists( 'is_post_publicly_viewable' ) && is_post_publicly_viewable( (int) $comment->comment_post_ID ) ) { + $link = get_permalink( (int) $comment->comment_post_ID ); + + if ( is_string( $link ) ) { + $redirect_to = $link; + $requested_redirect_to = $link; + } + } + } } if ( ! $new_user ) { @@ -291,7 +374,17 @@ protected static function get_redirect( WP_User $new_user = null, WP_User $old_u $redirect_to = apply_filters( 'login_redirect', $redirect_to, $requested_redirect_to, $new_user ); } - return $redirect_to; + /** + * Filters the redirect location after a user switches to another account or switches off. + * + * @since 1.7.0 + * + * @param string $redirect_to The target redirect location, or an empty string if none is specified. + * @param string|null $redirect_type The redirect type, see the `user_switching::REDIRECT_*` constants. + * @param WP_User|null $new_user The user being switched to, or null if there is none. + * @param WP_User|null $old_user The user being switched from, or null if there is none. + */ + return apply_filters( 'user_switching_redirect_to', $redirect_to, $redirect_type, $new_user, $old_user ); } /** @@ -300,76 +393,63 @@ protected static function get_redirect( WP_User $new_user = null, WP_User $old_u * @return void */ public function action_admin_notices() { - $user = wp_get_current_user(); + $user = wp_get_current_user(); $old_user = self::get_old_user(); if ( $old_user ) { $switched_locale = false; - $lang_attr = ''; - - if ( function_exists( 'get_user_locale' ) ) { - $locale = get_user_locale( $old_user ); - $switched_locale = switch_to_locale( $locale ); - $lang_attr = str_replace( '_', '-', $locale ); - } + $lang_attr = ''; + $locale = get_user_locale( $old_user ); + $switched_locale = switch_to_locale( $locale ); + $lang_attr = str_replace( '_', '-', $locale ); ?> -
+
', - esc_attr( $lang_attr ) - ); - } else { - echo '

'; - } + if ( $lang_attr ) { + printf( + '

', + esc_attr( $lang_attr ) + ); + } else { + echo '

'; + } ?> display_name, - $user->user_login - ) ); - } - $switch_back_url = add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), - ), self::switch_back_url( $old_user ) ); - - $message .= sprintf( - ' %s.', - esc_url( $switch_back_url ), - esc_html( sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switch back to %1$s (%2$s)', 'user-switching' ), - $old_user->display_name, - $old_user->user_login - ) ) - ); - - /** - * Filters the contents of the message that's displayed to switched users in the admin area. - * - * @since 1.1.0 - * - * @param string $message The message displayed to the switched user. - * @param WP_User $user The current user object. - * @param WP_User $old_user The old user object. - * @param string $switch_back_url The switch back URL. - * @param bool $just_switched Whether the user made the switch on this page request. - */ - $message = apply_filters( 'user_switching_switched_message', $message, $user, $old_user, $switch_back_url, $just_switched ); - - echo wp_kses( $message, array( - 'a' => array( - 'href' => array(), - ), - ) ); + $message = ''; + $just_switched = isset( $_GET['user_switched'] ); + if ( $just_switched ) { + $message = esc_html( self::switched_to_message( $user ) ); + } + $switch_back_url = add_query_arg( array( + 'redirect_to' => rawurlencode( self::current_url() ), + ), self::switch_back_url( $old_user ) ); + + $message .= sprintf( + ' %s.', + esc_url( $switch_back_url ), + esc_html( self::switch_back_message( $old_user ) ) + ); + + /** + * Filters the contents of the message that's displayed to switched users in the admin area. + * + * @since 1.1.0 + * + * @param string $message The message displayed to the switched user. + * @param WP_User $user The current user object. + * @param WP_User $old_user The old user object. + * @param string $switch_back_url The switch back URL. + * @param bool $just_switched Whether the user made the switch on this page request. + */ + $message = apply_filters( 'user_switching_switched_message', $message, $user, $old_user, $switch_back_url, $just_switched ); + + echo wp_kses( $message, array( + 'a' => array( + 'href' => array(), + ), + ) ); ?>

@@ -379,24 +459,14 @@ public function action_admin_notices() { } } elseif ( isset( $_GET['user_switched'] ) ) { ?> -
+

display_name, - $user->user_login - ) ); - } else { - echo esc_html( sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switched to %1$s (%2$s).', 'user-switching' ), - $user->display_name, - $user->user_login - ) ); - } + if ( isset( $_GET['switched_back'] ) ) { + echo esc_html( self::switched_back_message( $user ) ); + } else { + echo esc_html( self::switched_to_message( $user ) ); + } ?>

@@ -456,16 +526,10 @@ public function action_admin_bar_menu( WP_Admin_Bar $wp_admin_bar ) { return; } - if ( method_exists( $wp_admin_bar, 'get_node' ) ) { - if ( $wp_admin_bar->get_node( 'user-actions' ) ) { - $parent = 'user-actions'; - } else { - return; - } - } elseif ( get_option( 'show_avatars' ) ) { - $parent = 'my-account-with-avatar'; + if ( $wp_admin_bar->get_node( 'user-actions' ) ) { + $parent = 'user-actions'; } else { - $parent = 'my-account'; + return; } $old_user = self::get_old_user(); @@ -473,33 +537,30 @@ public function action_admin_bar_menu( WP_Admin_Bar $wp_admin_bar ) { if ( $old_user ) { $wp_admin_bar->add_node( array( 'parent' => $parent, - 'id' => 'switch-back', - 'title' => esc_html( sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switch back to %1$s (%2$s)', 'user-switching' ), - $old_user->display_name, - $old_user->user_login - ) ), - 'href' => add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), + 'id' => 'switch-back', + 'title' => esc_html( self::switch_back_message( $old_user ) ), + 'href' => add_query_arg( array( + 'redirect_to' => rawurlencode( self::current_url() ), ), self::switch_back_url( $old_user ) ), ) ); } if ( current_user_can( 'switch_off' ) ) { $url = self::switch_off_url( wp_get_current_user() ); - if ( ! is_admin() ) { - $url = add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), - ), $url ); + $redirect_to = is_admin() ? self::get_admin_redirect_to() : array( + 'redirect_to' => rawurlencode( self::current_url() ), + ); + + if ( is_array( $redirect_to ) ) { + $url = add_query_arg( $redirect_to, $url ); } $wp_admin_bar->add_node( array( 'parent' => $parent, - 'id' => 'switch-off', + 'id' => 'switch-off', /* Translators: "switch off" means to temporarily log out */ - 'title' => esc_html__( 'Switch Off', 'user-switching' ), - 'href' => $url, + 'title' => esc_html__( 'Switch Off', 'user-switching' ), + 'href' => $url, ) ); } @@ -507,30 +568,58 @@ public function action_admin_bar_menu( WP_Admin_Bar $wp_admin_bar ) { if ( $old_user ) { $wp_admin_bar->add_node( array( 'parent' => 'edit', - 'id' => 'author-switch-back', - 'title' => esc_html( sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switch back to %1$s (%2$s)', 'user-switching' ), - $old_user->display_name, - $old_user->user_login - ) ), - 'href' => add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), + 'id' => 'author-switch-back', + 'title' => esc_html( self::switch_back_message( $old_user ) ), + 'href' => add_query_arg( array( + 'redirect_to' => rawurlencode( self::current_url() ), ), self::switch_back_url( $old_user ) ), ) ); } elseif ( current_user_can( 'switch_to_user', get_queried_object_id() ) ) { $wp_admin_bar->add_node( array( 'parent' => 'edit', - 'id' => 'author-switch-to', - 'title' => esc_html__( 'Switch To', 'user-switching' ), - 'href' => add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), + 'id' => 'author-switch-to', + 'title' => esc_html__( 'Switch To', 'user-switching' ), + 'href' => add_query_arg( array( + 'redirect_to' => rawurlencode( self::current_url() ), ), self::switch_to_url( get_queried_object() ) ), ) ); } } } + /** + * Returns a context-aware redirect parameter for use when switching off in the admin area. + * + * This is used to redirect the user to the URL of the item they're editing at the time. + * + * @return ?array + */ + public static function get_admin_redirect_to() { + if ( ! empty( $_GET['post'] ) ) { + // Post + return array( + 'redirect_to_post' => intval( $_GET['post'] ), + ); + } elseif ( ! empty( $_GET['tag_ID'] ) ) { + // Term + return array( + 'redirect_to_term' => intval( $_GET['tag_ID'] ), + ); + } elseif ( ! empty( $_GET['user_id'] ) ) { + // User + return array( + 'redirect_to_user' => intval( $_GET['user_id'] ), + ); + } elseif ( ! empty( $_GET['c'] ) ) { + // Comment + return array( + 'redirect_to_comment' => intval( $_GET['c'] ), + ); + } + + return null; + } + /** * Adds a 'Switch back to {user}' link to the Meta sidebar widget. * @@ -540,19 +629,13 @@ public function action_wp_meta() { $old_user = self::get_old_user(); if ( $old_user instanceof WP_User ) { - $link = sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switch back to %1$s (%2$s)', 'user-switching' ), - $old_user->display_name, - $old_user->user_login - ); $url = add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), + 'redirect_to' => rawurlencode( self::current_url() ), ), self::switch_back_url( $old_user ) ); printf( '
  • %s
  • ', esc_url( $url ), - esc_html( $link ) + esc_html( self::switch_back_message( $old_user ) ) ); } } @@ -581,19 +664,13 @@ public function action_wp_footer() { $old_user = self::get_old_user(); if ( $old_user instanceof WP_User ) { - $link = sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switch back to %1$s (%2$s)', 'user-switching' ), - $old_user->display_name, - $old_user->user_login - ); $url = add_query_arg( array( - 'redirect_to' => urlencode( self::current_url() ), + 'redirect_to' => rawurlencode( self::current_url() ), ), self::switch_back_url( $old_user ) ); printf( - '

    %s

    ', + '

    %s

    ', esc_url( $url ), - esc_html( $link ) + esc_html( self::switch_back_message( $old_user ) ) ); } } @@ -608,12 +685,6 @@ public function filter_login_message( $message ) { $old_user = self::get_old_user(); if ( $old_user instanceof WP_User ) { - $link = sprintf( - /* Translators: 1: user display name; 2: username; */ - __( 'Switch back to %1$s (%2$s)', 'user-switching' ), - $old_user->display_name, - $old_user->user_login - ); $url = self::switch_back_url( $old_user ); if ( ! empty( $_REQUEST['interim-login'] ) ) { @@ -622,7 +693,7 @@ public function filter_login_message( $message ) { ), $url ); } elseif ( ! empty( $_REQUEST['redirect_to'] ) ) { $url = add_query_arg( array( - 'redirect_to' => urlencode( wp_unslash( $_REQUEST['redirect_to'] ) ), + 'redirect_to' => rawurlencode( wp_unslash( $_REQUEST['redirect_to'] ) ), ), $url ); } @@ -631,7 +702,7 @@ public function filter_login_message( $message ) { $message .= sprintf( '%2$s', esc_url( $url ), - esc_html( $link ) + esc_html( self::switch_back_message( $old_user ) ) ); $message .= '

    '; } @@ -642,9 +713,9 @@ public function filter_login_message( $message ) { /** * Adds a 'Switch To' link to each list of user actions on the Users screen. * - * @param string[] $actions Array of actions to display for this user row. - * @param WP_User $user The user object displayed in this row. - * @return string[] Array of actions to display for this user row. + * @param array $actions Array of actions to display for this user row. + * @param WP_User $user The user object displayed in this row. + * @return array Array of actions to display for this user row. */ public function filter_user_row_actions( array $actions, WP_User $user ) { $link = self::maybe_switch_url( $user ); @@ -687,16 +758,16 @@ public function action_bp_button() { } $link = add_query_arg( array( - 'redirect_to' => urlencode( bp_core_get_user_domain( $user->ID ) ), + 'redirect_to' => rawurlencode( bp_core_get_user_domain( $user->ID ) ), ), $link ); $components = array_keys( buddypress()->active_components ); echo bp_get_button( array( - 'id' => 'user_switching', - 'component' => reset( $components ), - 'link_href' => esc_url( $link ), - 'link_text' => esc_html__( 'Switch To', 'user-switching' ), + 'id' => 'user_switching', + 'component' => reset( $components ), + 'link_href' => esc_url( $link ), + 'link_text' => esc_html__( 'Switch To', 'user-switching' ), 'wrapper_id' => 'user_switching_switch_to', ) ); } @@ -720,7 +791,7 @@ public function action_bbpress_button() { } $link = add_query_arg( array( - 'redirect_to' => urlencode( bbp_get_user_profile_url( $user->ID ) ), + 'redirect_to' => rawurlencode( bbp_get_user_profile_url( $user->ID ) ), ), $link ); echo '
      '; @@ -732,13 +803,34 @@ public function action_bbpress_button() { echo '
    '; } + /** + * Filters the array of row meta for each plugin in the Plugins list table. + * + * @param array $plugin_meta An array of the plugin row's meta data. + * @param string $plugin_file Path to the plugin file relative to the plugins directory. + * @return array An array of the plugin row's meta data. + */ + public function filter_plugin_row_meta( array $plugin_meta, $plugin_file ) { + if ( 'user-switching/user-switching.php' !== $plugin_file ) { + return $plugin_meta; + } + + $plugin_meta[] = sprintf( + '%2$s', + 'https://github.com/sponsors/johnbillion', + esc_html_x( 'Sponsor', 'verb', 'user-switching' ) + ); + + return $plugin_meta; + } + /** * Filters the list of query arguments which get removed from admin area URLs in WordPress. * * @link https://core.trac.wordpress.org/ticket/23367 * - * @param string[] $args Array of removable query arguments. - * @return string[] Updated array of removable query arguments. + * @param array $args Array of removable query arguments. + * @return array Updated array of removable query arguments. */ public function filter_removable_query_args( array $args ) { return array_merge( $args, array( @@ -774,9 +866,9 @@ public static function maybe_switch_url( WP_User $user ) { */ public static function switch_to_url( WP_User $user ) { return wp_nonce_url( add_query_arg( array( - 'action' => 'switch_to_user', + 'action' => 'switch_to_user', 'user_id' => $user->ID, - 'nr' => 1, + 'nr' => 1, ), wp_login_url() ), "switch_to_user_{$user->ID}" ); } @@ -789,7 +881,7 @@ public static function switch_to_url( WP_User $user ) { public static function switch_back_url( WP_User $user ) { return wp_nonce_url( add_query_arg( array( 'action' => 'switch_to_olduser', - 'nr' => 1, + 'nr' => 1, ), wp_login_url() ), "switch_to_olduser_{$user->ID}" ); } @@ -802,10 +894,73 @@ public static function switch_back_url( WP_User $user ) { public static function switch_off_url( WP_User $user ) { return wp_nonce_url( add_query_arg( array( 'action' => 'switch_off', - 'nr' => 1, + 'nr' => 1, ), wp_login_url() ), "switch_off_{$user->ID}" ); } + /** + * Returns the message shown to the user when they've switched to a user. + * + * @param WP_User $user The concerned user. + * @return string The message. + */ + public static function switched_to_message( WP_User $user ) { + $message = sprintf( + /* Translators: 1: user display name; 2: username; */ + __( 'Switched to %1$s (%2$s).', 'user-switching' ), + $user->display_name, + $user->user_login + ); + + // Removes the user login from this message without invalidating existing translations + return str_replace( sprintf( + ' (%s)', + $user->user_login + ), '', $message ); + } + + /** + * Returns the message shown to the user for the link to switch back to their original user. + * + * @param WP_User $user The concerned user. + * @return string The message. + */ + public static function switch_back_message( WP_User $user ) { + $message = sprintf( + /* Translators: 1: user display name; 2: username; */ + __( 'Switch back to %1$s (%2$s)', 'user-switching' ), + $user->display_name, + $user->user_login + ); + + // Removes the user login from this message without invalidating existing translations + return str_replace( sprintf( + ' (%s)', + $user->user_login + ), '', $message ); + } + + /** + * Returns the message shown to the user when they've switched back to their original user. + * + * @param WP_User $user The concerned user. + * @return string The message. + */ + public static function switched_back_message( WP_User $user ) { + $message = sprintf( + /* Translators: 1: user display name; 2: username; */ + __( 'Switched back to %1$s (%2$s).', 'user-switching' ), + $user->display_name, + $user->user_login + ); + + // Removes the user login from this message without invalidating existing translations + return str_replace( sprintf( + ' (%s)', + $user->user_login + ), '', $message ); + } + /** * Returns the current URL. * @@ -818,15 +973,11 @@ public static function current_url() { /** * Removes a list of common confirmation-style query args from a URL. * - * @param string $url A URL. + * @param string $url A URL. * @return string The URL with query args removed. */ public static function remove_query_args( $url ) { - if ( function_exists( 'wp_removable_query_args' ) ) { - $url = remove_query_arg( wp_removable_query_args(), $url ); - } - - return $url; + return remove_query_arg( wp_removable_query_args(), $url ); } /** @@ -839,7 +990,7 @@ public static function remove_query_args( $url ) { * @return bool Should the old user cookie be secure? */ public static function secure_olduser_cookie() { - return ( is_ssl() && ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) ) ); + return ( is_ssl() && ( 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME ) ) ); } /** @@ -850,7 +1001,80 @@ public static function secure_olduser_cookie() { * @return bool Whether the auth cookie should be secure. */ public static function secure_auth_cookie() { - return ( is_ssl() && ( 'https' === parse_url( wp_login_url(), PHP_URL_SCHEME ) ) ); + return ( is_ssl() && ( 'https' === wp_parse_url( wp_login_url(), PHP_URL_SCHEME ) ) ); + } + + /** + * Adds a 'Switch back to {user}' link to the WooCommerce login screen. + * + * @return void + */ + public function action_woocommerce_login_form_start() { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->filter_login_message( '' ); + } + + /** + * Adds a 'Switch To' link to the WooCommerce order screen. + * + * @param WC_Order $order The WooCommerce order object. + * @return void + */ + public function action_woocommerce_order_details( WC_Order $order ) { + $user = $order->get_user(); + + if ( ! $user || ! current_user_can( 'switch_to_user', $user->ID ) ) { + return; + } + + $url = add_query_arg( array( + 'redirect_to' => rawurlencode( $order->get_view_order_url() ), + ), self::switch_to_url( $user ) ); + + printf( + '

    %2$s

    ', + esc_url( $url ), + esc_html__( 'Switch To', 'user-switching' ) + ); + } + + /** + * Adds a 'Switch back to {user}' link to the My Account screen in WooCommerce. + * + * @param array $items Menu items. + * @return array Menu items. + */ + public function filter_woocommerce_account_menu_items( array $items ) { + $old_user = self::get_old_user(); + + if ( ! $old_user ) { + return $items; + } + + $items['user-switching-switch-back'] = self::switch_back_message( $old_user ); + + return $items; + } + + /** + * Sets the URL of the 'Switch back to {user}' link in the My Account screen in WooCommerce. + * + * @param string $url The URL for the menu item. + * @param string $endpoint The endpoint slug for the menu item. + * @return string The URL for the menu item. + */ + public function filter_woocommerce_get_endpoint_url( $url, $endpoint ) { + if ( 'user-switching-switch-back' !== $endpoint ) { + return $url; + } + + $old_user = self::get_old_user(); + + if ( ! $old_user ) { + return $url; + } + + return self::switch_back_url( $old_user ); } /** @@ -880,24 +1104,25 @@ public function forget_woocommerce_session() { * Filters a user's capabilities so they can be altered at runtime. * * This is used to: + * * - Grant the 'switch_to_user' capability to the user if they have the ability to edit the user they're trying to * switch to (and that user is not themselves). * - Grant the 'switch_off' capability to the user if they can edit other users. * * Important: This does not get called for Super Admins. See filter_map_meta_cap() below. * - * @param bool[] $user_caps Array of key/value pairs where keys represent a capability name and boolean values - * represent whether the user has that capability. - * @param string[] $required_caps Array of required primitive capabilities for the requested capability. - * @param mixed[] $args { + * @param array $user_caps Array of key/value pairs where keys represent a capability name and boolean values + * represent whether the user has that capability. + * @param array $required_caps Array of required primitive capabilities for the requested capability. + * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters. * } - * @param WP_User $user Concerned user object. - * @return bool[] Array of concerned user's capabilities. + * @param WP_User $user Concerned user object. + * @return array Array of concerned user's capabilities. */ public function filter_user_has_cap( array $user_caps, array $required_caps, array $args, WP_User $user ) { if ( 'switch_to_user' === $args[0] ) { @@ -927,20 +1152,21 @@ public function filter_user_has_cap( array $user_caps, array $required_caps, arr * Filters the required primitive capabilities for the given primitive or meta capability. * * This is used to: + * * - Add the 'do_not_allow' capability to the list of required capabilities when a Super Admin is trying to switch * to themselves. * * It affects nothing else as Super Admins can do everything by default. * - * @param string[] $required_caps Array of required primitive capabilities for the requested capability. - * @param string $cap Capability or meta capability being checked. - * @param int $user_id Concerned user ID. - * @param mixed[] $args { + * @param array $required_caps Array of required primitive capabilities for the requested capability. + * @param string $cap Capability or meta capability being checked. + * @param int $user_id Concerned user ID. + * @param array $args { * Arguments that accompany the requested capability check. * * @type mixed ...$0 Optional second and further parameters. * } - * @return string[] Array of required capabilities for the requested action. + * @return array Array of required capabilities for the requested action. */ public function filter_map_meta_cap( array $required_caps, $cap, $user_id, array $args ) { if ( 'switch_to_user' === $cap ) { @@ -969,8 +1195,7 @@ public static function get_instance() { /** * Private class constructor. Use `get_instance()` to get the instance. */ - final private function __construct() {} - + private function __construct() {} } if ( ! function_exists( 'user_switching_set_olduser_cookie' ) ) { @@ -985,18 +1210,18 @@ final private function __construct() {} * @return void */ function user_switching_set_olduser_cookie( $old_user_id, $pop = false, $token = '' ) { - $secure_auth_cookie = user_switching::secure_auth_cookie(); + $secure_auth_cookie = user_switching::secure_auth_cookie(); $secure_olduser_cookie = user_switching::secure_olduser_cookie(); - $expiration = time() + 172800; // 48 hours - $auth_cookie = user_switching_get_auth_cookie(); - $olduser_cookie = wp_generate_auth_cookie( $old_user_id, $expiration, 'logged_in', $token ); + $expiration = time() + 172800; // 48 hours + $auth_cookie = user_switching_get_auth_cookie(); + $olduser_cookie = wp_generate_auth_cookie( $old_user_id, $expiration, 'logged_in', $token ); if ( $secure_auth_cookie ) { $auth_cookie_name = USER_SWITCHING_SECURE_COOKIE; - $scheme = 'secure_auth'; + $scheme = 'secure_auth'; } else { $auth_cookie_name = USER_SWITCHING_COOKIE; - $scheme = 'auth'; + $scheme = 'auth'; } if ( $pop ) { @@ -1005,7 +1230,7 @@ function user_switching_set_olduser_cookie( $old_user_id, $pop = false, $token = array_push( $auth_cookie, wp_generate_auth_cookie( $old_user_id, $expiration, $scheme, $token ) ); } - $auth_cookie = json_encode( $auth_cookie ); + $auth_cookie = wp_json_encode( $auth_cookie ); if ( false === $auth_cookie ) { return; @@ -1124,7 +1349,7 @@ function user_switching_get_olduser_cookie() { /** * Gets the value of the auth cookie containing the list of originating users. * - * @return string[] Array of originating user authentication cookie values. Empty array if there are none. + * @return array Array of originating user authentication cookie values. Empty array if there are none. */ function user_switching_get_auth_cookie() { if ( user_switching::secure_auth_cookie() ) { @@ -1159,10 +1384,10 @@ function switch_to_user( $user_id, $remember = false, $set_old_user = true ) { return false; } - $old_user_id = ( is_user_logged_in() ) ? get_current_user_id() : false; - $old_token = function_exists( 'wp_get_session_token' ) ? wp_get_session_token() : ''; + $old_user_id = ( is_user_logged_in() ) ? get_current_user_id() : false; + $old_token = wp_get_session_token(); $auth_cookies = user_switching_get_auth_cookie(); - $auth_cookie = end( $auth_cookies ); + $auth_cookie = end( $auth_cookies ); $cookie_parts = $auth_cookie ? wp_parse_auth_cookie( $auth_cookie ) : false; if ( $set_old_user && $old_user_id ) { @@ -1171,24 +1396,23 @@ function switch_to_user( $user_id, $remember = false, $set_old_user = true ) { user_switching_set_olduser_cookie( $old_user_id, false, $old_token ); } else { // Switching back, either after being switched off or after being switched to another user - $new_token = ( $cookie_parts && isset( $cookie_parts['token'] ) ) ? $cookie_parts['token'] : ''; + $new_token = $cookie_parts['token'] ?? ''; user_switching_clear_olduser_cookie( false ); } /** * Attaches the original user ID and session token to the new session when a user switches to another user. * - * @param array $session Array of extra data. - * @param int $user_id User ID. - * @return array Array of extra data. + * @param array $session Array of extra data. + * @return array Array of extra data. */ - $session_filter = function( array $session, $user_id ) use ( $old_user_id, $old_token ) { - $session['switched_from_id'] = $old_user_id; + $session_filter = function ( array $session ) use ( $old_user_id, $old_token ) { + $session['switched_from_id'] = $old_user_id; $session['switched_from_session'] = $old_token; return $session; }; - add_filter( 'attach_session_information', $session_filter, 99, 2 ); + add_filter( 'attach_session_information', $session_filter, 99 ); wp_clear_auth_cookie(); wp_set_auth_cookie( $user_id, $remember, '', $new_token ); @@ -1196,7 +1420,7 @@ function switch_to_user( $user_id, $remember = false, $set_old_user = true ) { remove_filter( 'attach_session_information', $session_filter, 99 ); - if ( $set_old_user ) { + if ( $set_old_user && $old_user_id ) { /** * Fires when a user switches to another user account. * @@ -1251,7 +1475,7 @@ function switch_off_user() { return false; } - $old_token = function_exists( 'wp_get_session_token' ) ? wp_get_session_token() : ''; + $old_token = wp_get_session_token(); user_switching_set_olduser_cookie( $old_user_id, false, $old_token ); wp_clear_auth_cookie();