diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1862ba1..2f84457 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,3 +11,7 @@ on: jobs: test: uses: wp-cli/.github/.github/workflows/reusable-testing.yml@main + with: + minimum-wp: '4.9' + minimum-php: '7.2' + with-coverage: false diff --git a/README.md b/README.md index 0257749..77095de 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Tool to reset WordPress database. This reset WP database but retains given admin -Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) +Quick links: [Using](#using) | [Installing](#installing) ## Using @@ -25,7 +25,7 @@ wp database reset --author= ## Installing -Installing this package requires WP-CLI v2.11 or greater. Update to the latest stable release with `wp cli update`. +Installing this package requires WP-CLI v3.0 or greater. Update to the latest stable release with `wp cli update`. Once you've done so, you can install the latest stable version of this package with: @@ -36,30 +36,8 @@ wp package install ernilambar/database-command:@stable To install the latest development version of this package, use the following command instead: ```bash -wp package install ernilambar/database-command:dev-master +wp package install ernilambar/database-command:dev-main ``` -## Contributing - -We appreciate you taking the initiative to contribute to this project. - -Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. - -For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. - -### Reporting a bug - -Think you’ve found a bug? We’d love for you to help us get it fixed. - -Before you create a new issue, you should [search existing issues](https://github.com/ernilambar/database-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. - -Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/ernilambar/database-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). - -### Creating a pull request - -Want to contribute a new feature? Please first [open a new issue](https://github.com/ernilambar/database-command/issues/new) to discuss whether the feature is a good fit for the project. - -Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. - *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* diff --git a/composer.json b/composer.json index 8954bee..aa960b4 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,15 @@ ], "homepage": "https://github.com/ernilambar/database-command", "require": { - "wp-cli/wp-cli": "^2.11" + "php": ">=7.2.24 || ^8.0", + "wp-cli/wp-cli": "^3.0" }, "require-dev": { - "wp-cli/entity-command": "^2", - "wp-cli/scaffold-package-command": "^2", - "wp-cli/wp-cli-tests": "^4" + "wp-cli/db-command": "^3.0", + "wp-cli/entity-command": "^3.0", + "wp-cli/role-command": "^3.0", + "wp-cli/scaffold-package-command": "^3.0", + "wp-cli/wp-cli-tests": "^5.2" }, "minimum-stability": "dev", "prefer-stable": true, @@ -37,8 +40,11 @@ "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, - "ergebnis/composer-normalize": true + "ergebnis/composer-normalize": true, + "johnpbloch/wordpress-core-installer": true, + "phpstan/extension-installer": true }, + "optimize-autoloader": true, "process-timeout": 7200, "sort-packages": true }, @@ -49,8 +55,7 @@ "readme": { "sections": [ "Using", - "Installing", - "Contributing" + "Installing" ] } }, @@ -60,9 +65,10 @@ "lint": "run-linter-tests", "phpcbf": "run-phpcbf-cleanup", "phpcs": "run-phpcs-tests", + "phpstan": "run-phpstan-tests", "phpunit": "run-php-unit-tests", "prepare-tests": "install-package-tests", - "readme": "./vendor/bin/wp scaffold package-readme . --force", + "readme": "./vendor/bin/wp scaffold package-readme . --force --branch=main", "test": [ "@lint", "@phpcs", diff --git a/features/database-command-basic.feature b/features/database-command-basic.feature index 27da089..7e4729d 100644 --- a/features/database-command-basic.feature +++ b/features/database-command-basic.feature @@ -28,6 +28,87 @@ Feature: Basic tests missing --author parameter (Administrator user you want to keep after reset.) """ + Scenario: Test empty author parameter value + Given a WP install + + When I try `wp database reset --author=""` + Then STDERR should be: + """ + Error: User does not exist. + """ + + Scenario: Test whitespace-only author parameter value + Given a WP install + + When I try `wp database reset --author=" "` + Then STDERR should be: + """ + Error: User does not exist. + """ + + Scenario: Test reset when multiple administrators exist + Given a WP install + + When I run `wp user create firstadmin firstadmin@gmail.com --role=administrator` + And I run `wp user create secondadmin secondadmin@gmail.com --role=administrator` + And I run `wp database reset --author=secondadmin` + And I run `wp user list --role=administrator --format=count` + Then STDOUT should be: + """ + 1 + """ + + Scenario: Test reset accepts administrator usernames with punctuation + Given a WP install + + When I run `wp user create admin.name admin.name@gmail.com --role=administrator` + And I run `wp database reset --author=admin.name` + Then STDOUT should contain: + """ + Success: Database is reset successfully. + """ + + @skip-sqlite + Scenario: Test reset fails when wp_install returns an error + Given a WP install + And a wp-content/mu-plugins/simulate-install-failure.php file: + """ + add_cap( "manage_options" );'` + And I run `wp database reset --author=customadminuser` + Then STDOUT should contain: + """ + Success: Database is reset successfully. + """ + + Scenario: Test repeated reset with the same administrator + Given a WP install + + When I run `wp user create repeatadmin repeatadmin@gmail.com --role=administrator` + And I run `wp database reset --author=repeatadmin` + And I run `wp database reset --author=repeatadmin` + Then STDOUT should contain: + """ + Success: Database is reset successfully. + """ + Scenario: Test author parameter is passed but non-existent user Given a WP install @@ -56,3 +137,85 @@ Feature: Basic tests """ Success: Database is reset successfully. """ + + Scenario: Test missing author parameter is caught before multisite check runs + Given a WP multisite subdirectory install + + When I try `wp database reset` + Then STDERR should contain: + """ + missing --author parameter + """ + + Scenario: Test author with leading/trailing whitespace is trimmed + Given a WP install + + When I run `wp user create spacedadmin spacedadmin@gmail.com --role=administrator` + And I run `wp database reset --author=" spacedadmin "` + Then STDOUT should contain: + """ + Success: Database is reset successfully. + """ + + @skip-sqlite + Scenario: Test author lookup is case-insensitive on collations that support it + Given a WP install + + When I run `wp user create caseadmin caseadmin@gmail.com --role=administrator` + And I run `wp database reset --author=CASEADMIN` + Then STDOUT should contain: + """ + Success: Database is reset successfully. + """ + + Scenario: Test author as user ID does not match a username + Given a WP install + + When I run `wp user create idadmin idadmin@gmail.com --role=administrator` + And I run `wp user get idadmin --field=ID` + And save STDOUT as {USER_ID} + And I try `wp database reset --author={USER_ID}` + Then STDERR should be: + """ + Error: User does not exist. + """ + + Scenario: Test author as email does not match a username + Given a WP install + + When I run `wp user create emailadmin emailadmin@gmail.com --role=administrator` + And I try `wp database reset --author=emailadmin@gmail.com` + Then STDERR should be: + """ + Error: User does not exist. + """ + + Scenario: Test custom role without manage_options capability fails + Given a WP install + + When I run `wp role create customnorole "Custom NoRole"` + And I run `wp user create noroleuser noroleuser@gmail.com --role=customnorole` + And I try `wp database reset --author=noroleuser` + Then STDERR should be: + """ + Error: User is not administrator. + """ + + Scenario: Test editor role cannot be used as author + Given a WP install + + When I run `wp user create testeditor testeditor@gmail.com --role=editor` + And I try `wp database reset --author=testeditor` + Then STDERR should be: + """ + Error: User is not administrator. + """ + + Scenario: Test help shows author parameter + Given an empty directory + + When I try `PAGER= wp help database reset` + Then STDOUT should contain: + """ + --author + """ diff --git a/features/database-command-reset.feature b/features/database-command-reset.feature index 78e3a09..c022747 100644 --- a/features/database-command-reset.feature +++ b/features/database-command-reset.feature @@ -86,3 +86,84 @@ Feature: Test reset behaviour """ {SITE_URL} """ + + Scenario: Test reset preserves blog_public option + Given a WP install + + When I run `wp user create testadmin_bp testadmin_bp@gmail.com --role=administrator` + And I run `wp option set blog_public 0` + And I run `wp database reset --author=testadmin_bp` + And I run `wp option get blog_public` + Then STDOUT should be: + """ + 0 + """ + + Scenario: Test reset preserves home and siteurl when customized + Given a WP install + + When I run `wp user create testadmin_home testadmin_home@gmail.com --role=administrator` + And I run `wp option set home https://example-home.test` + And I run `wp option set siteurl https://example-site.test` + And I run `wp database reset --author=testadmin_home` + And I run `wp option get home` + Then STDOUT should be: + """ + https://example-home.test + """ + When I run `wp option get siteurl` + Then STDOUT should be: + """ + https://example-site.test + """ + + @skip-sqlite + Scenario: Test reset removes prefixed custom tables but keeps non-prefixed + Given a WP install + + When I run `wp db query "CREATE TABLE wp_custom_test_reset (id INT PRIMARY KEY)"` + And I run `wp db query "CREATE TABLE custom_noprefix_reset (id INT PRIMARY KEY)"` + And I run `wp user create testadmin_ct testadmin_ct@gmail.com --role=administrator` + And I run `wp database reset --author=testadmin_ct` + And I run `wp db query "SHOW TABLES LIKE 'wp_custom_test_reset'" --skip-column-names` + Then STDOUT should not contain: + """ + wp_custom_test_reset + """ + When I run `wp db query "SHOW TABLES LIKE 'custom_noprefix_reset'" --skip-column-names` + Then STDOUT should contain: + """ + custom_noprefix_reset + """ + + Scenario: Test previous admin is removed after sequential resets + Given a WP install + + When I run `wp user create adminA adminA@gmail.com --role=administrator` + And I run `wp database reset --author=adminA` + And I run `wp user create adminB adminB@gmail.com --role=administrator` + And I run `wp database reset --author=adminB` + And I try `wp user get adminA --field=login` + Then STDERR should contain: + """ + Invalid user + """ + + @skip-sqlite + Scenario: Test reset clears activation key and password nag + Given a WP install + + When I run `wp user create testadmin_meta testadmin_meta@gmail.com --role=administrator` + And I run `wp eval 'update_user_meta( get_user_by( "login", "testadmin_meta" )->ID, "default_password_nag", true );'` + And I run `wp db query "UPDATE wp_users SET user_activation_key='testkey123' WHERE user_login='testadmin_meta'"` + And I run `wp database reset --author=testadmin_meta` + And I run `wp db query "SELECT user_activation_key FROM wp_users WHERE user_login='testadmin_meta'" --skip-column-names` + Then STDOUT should not contain: + """ + testkey123 + """ + When I run `wp eval 'echo get_user_meta( get_user_by( "login", "testadmin_meta" )->ID, "default_password_nag", true ) ? "1" : "0";'` + Then STDOUT should be: + """ + 0 + """ diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..b645012 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,10 @@ +parameters: + level: 8 + paths: + - src + - command.php + scanDirectories: + - vendor/wp-cli/wp-cli/php + scanFiles: + - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php + treatPhpDocTypesAsCertain: false diff --git a/src/DatabaseCommand.php b/src/DatabaseCommand.php index 7acfaec..f472ed1 100644 --- a/src/DatabaseCommand.php +++ b/src/DatabaseCommand.php @@ -20,10 +20,14 @@ class DatabaseCommand extends WP_CLI_Command { * # Reset database and keep `admin` user. * $ wp database reset --author=admin * + * @since 1.0.0 + * + * @return void + * * @when after_wp_load * - * @param array $args Indexed array of positional arguments. - * @param array $assoc_args Associative array of associative arguments. + * @param array $args Indexed array of positional arguments. + * @param array $assoc_args Associative array of associative arguments. */ public function reset( $args, $assoc_args ) { // Bail if multisite. @@ -39,6 +43,14 @@ public function reset( $args, $assoc_args ) { $author = $assoc_args['author']; + if ( is_string( $author ) ) { + $author = trim( $author ); + } + + if ( empty( $author ) ) { + WP_CLI::error( 'User does not exist.' ); + } + $author_obj = \get_user_by( 'login', $author ); if ( false === $author_obj ) { @@ -55,15 +67,25 @@ public function reset( $args, $assoc_args ) { /** * Reset database. * + * @since 1.0.0 + * + * @return void + * * @access private * - * @param WP_User $user WP_User object. + * @param \WP_User $user WP_User object. */ - private function reset_callback( $user ) { + private function reset_callback( \WP_User $user ) { WP_CLI::log( 'Resetting...' ); // We don't want email notification. if ( ! function_exists( 'wp_new_blog_notification' ) ) { + /** + * @since 1.0.0 + * + * @return void + */ + // @phpstan-ignore function.inner function wp_new_blog_notification() { // Silence is golden. } @@ -74,15 +96,25 @@ function wp_new_blog_notification() { $blogname = \get_option( 'blogname' ); $blog_public = \get_option( 'blog_public' ); $siteurl = \get_option( 'siteurl' ); + $home = \get_option( 'home' ); + /** + * WordPress database access abstraction object. + * + * @var \wpdb $wpdb + */ global $wpdb; - $prefix = str_replace( '_', '\_', $wpdb->prefix ); + $prefix = $wpdb->esc_like( $wpdb->prefix ); $tables = $wpdb->get_col( "SHOW TABLES LIKE '{$prefix}%'" ); // phpcs:ignore WordPress.DB.PreparedSQL foreach ( $tables as $table ) { $wpdb->query( "DROP TABLE $table" ); // phpcs:ignore WordPress.DB.PreparedSQL + + if ( ! empty( $wpdb->last_error ) ) { + WP_CLI::error( "Failed to drop table {$table} ({$wpdb->last_error})." ); + } } // Set site URL. @@ -98,9 +130,26 @@ function wp_new_blog_notification() { WP_CLI::error( 'Resetting produced database errors, and may have partially or completely failed.' ); } + // Restore siteurl and home if they were customized. + if ( $siteurl ) { + \update_option( 'siteurl', $siteurl ); + } + if ( $home ) { + \update_option( 'home', $home ); + } + $user_id = isset( $result['user_id'] ) ? absint( $result['user_id'] ) : 0; - $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->users SET user_pass = %s, user_activation_key = '' WHERE ID = %d", $user->user_pass, $user_id ) ); + $wpdb->update( + $wpdb->users, + array( + 'user_pass' => $user->user_pass, + 'user_activation_key' => '', + ), + array( 'ID' => $user_id ), + array( '%s', '%s' ), + array( '%d' ) + ); // Fix password update nag. \update_user_meta( $user_id, 'default_password_nag', false );