!C99Shell v. 2.5 [PHP 8 Update] [24.05.2025]!

Software: Apache. PHP/8.3.27 

uname -a: Linux pdx1-shared-a4-04 6.6.104-grsec-jammy+ #3 SMP Tue Sep 16 00:28:11 UTC 2025 x86_64 

uid=6659440(dh_z2jmpm) gid=2086089(pg10499364) groups=2086089(pg10499364)  

Safe-mode: OFF (not secure)

/usr/local/wp/vendor/wp-cli/db-command/src/   drwxr-xr-x
Free 714.48 GB of 879.6 GB (81.23%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Self remove    Logout    


Viewing file:     DB_Command.php (70.51 KB)      -rw-r--r--
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php

use WP_CLI\Formatter;
use WP_CLI\Utils;

/**
 * Performs basic database operations using credentials stored in wp-config.php.
 *
 * ## EXAMPLES
 *
 *     # Create a new database.
 *     $ wp db create
 *     Success: Database created.
 *
 *     # Drop an existing database.
 *     $ wp db drop --yes
 *     Success: Database dropped.
 *
 *     # Reset the current database.
 *     $ wp db reset --yes
 *     Success: Database reset.
 *
 *     # Execute a SQL query stored in a file.
 *     $ wp db query < debug.sql
 *
 * @when after_wp_config_load
 */
class DB_Command extends WP_CLI_Command {

    /**
     * Legacy UTF-8 encoding for MySQL.
     *
     * @var string
     */
    const ENCODING_UTF8 = 'utf8';

    /**
     * Standards-compliant UTF-8 encoding for MySQL.
     *
     * @var string
     */
    const ENCODING_UTF8MB4 = 'utf8mb4';

    /**
     * A list of incompatible SQL modes.
     *
     * Copied over from WordPress Core code.
     * @see https://github.com/WordPress/wordpress-develop/blob/5.4.0/src/wp-includes/wp-db.php#L559-L572
     *
     * @var string[] Array of SQL mode names that are incompatible with WordPress.
     */
    protected $sql_incompatible_modes = [
        'NO_ZERO_DATE',
        'ONLY_FULL_GROUP_BY',
        'STRICT_TRANS_TABLES',
        'STRICT_ALL_TABLES',
        'TRADITIONAL',
        'ANSI',
    ];

    /**
     * Creates a new database.
     *
     * Runs `CREATE_DATABASE` SQL statement using `DB_HOST`, `DB_NAME`,
     * `DB_USER` and `DB_PASSWORD` database credentials specified in
     * wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db create
     *     Success: Database created.
     */
    public function create( $_, $assoc_args ) {

        $this->run_query( self::get_create_query(), $assoc_args );

        WP_CLI::success( 'Database created.' );
    }

    /**
     * Deletes the existing database.
     *
     * Runs `DROP_DATABASE` SQL statement using `DB_HOST`, `DB_NAME`,
     * `DB_USER` and `DB_PASSWORD` database credentials specified in
     * wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db drop --yes
     *     Success: Database dropped.
     */
    public function drop( $_, $assoc_args ) {
        WP_CLI::confirm( "Are you sure you want to drop the '" . DB_NAME . "' database?", $assoc_args );

        $this->run_query( sprintf( 'DROP DATABASE `%s`', DB_NAME ), $assoc_args );

        WP_CLI::success( 'Database dropped.' );
    }

    /**
     * Removes all tables from the database.
     *
     * Runs `DROP_DATABASE` and `CREATE_DATABASE` SQL statements using
     * `DB_HOST`, `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db reset --yes
     *     Success: Database reset.
     */
    public function reset( $_, $assoc_args ) {
        WP_CLI::confirm( "Are you sure you want to reset the '" . DB_NAME . "' database?", $assoc_args );

        $this->run_query( sprintf( 'DROP DATABASE IF EXISTS `%s`', DB_NAME ), $assoc_args );
        $this->run_query( self::get_create_query(), $assoc_args );

        WP_CLI::success( 'Database reset.' );
    }

    /**
     * Removes all tables with `$table_prefix` from the database.
     *
     * Runs `DROP_TABLE` for each table that has a `$table_prefix` as specified
     * in wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Delete all tables that match the current site prefix.
     *     $ wp db clean --yes
     *     Success: Tables dropped.
     *
     * @when after_wp_load
     */
    public function clean( $_, $assoc_args ) {
        global $wpdb;

        WP_CLI::confirm(
            sprintf(
                "Are you sure you want to drop all the tables on '%s' that use the current site's database prefix ('%s')?",
                DB_NAME,
                $wpdb->get_blog_prefix()
            ),
            $assoc_args
        );

        $tables = Utils\wp_get_table_names(
            [],
            [ 'all-tables-with-prefix' => true ]
        );

        foreach ( $tables as $table ) {
            $this->run_query(
                sprintf(
                    'DROP TABLE IF EXISTS `%s`.`%s`',
                    DB_NAME,
                    $table
                ),
                $assoc_args
            );
        }

        WP_CLI::success( 'Tables dropped.' );
    }

    /**
     * Checks the current status of the database.
     *
     * Runs `mysqlcheck` utility with `--check` using `DB_HOST`,
     * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * [See docs](http://dev.mysql.com/doc/refman/5.7/en/check-table.html)
     * for more details on the `CHECK TABLE` statement.
     *
     * This command does not check whether WordPress is installed;
     * to do that run `wp core is-installed`.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqlcheck. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqlcheck. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqlcheck. [Refer to mysqlcheck docs](https://dev.mysql.com/doc/en/mysqlcheck.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db check
     *     Success: Database checked.
     */
    public function check( $_, $assoc_args ) {

        $command = sprintf(
            '/usr/bin/env %s%s %s',
            Utils\get_sql_check_command(),
            $this->get_defaults_flag_string( $assoc_args ),
            '%s'
        );
        WP_CLI::debug( "Running shell command: {$command}", 'db' );

        $assoc_args['check'] = true;
        self::run(
            Utils\esc_cmd( $command, DB_NAME ),
            $assoc_args
        );

        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );
        WP_CLI::success( 'Database checked.' );
    }

    /**
     * Optimizes the database.
     *
     * Runs `mysqlcheck` utility with `--optimize=true` using `DB_HOST`,
     * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * [See docs](http://dev.mysql.com/doc/refman/5.7/en/optimize-table.html)
     * for more details on the `OPTIMIZE TABLE` statement.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqlcheck. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqlcheck. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqlcheck. [Refer to mysqlcheck docs](https://dev.mysql.com/doc/en/mysqlcheck.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db optimize
     *     Success: Database optimized.
     */
    public function optimize( $_, $assoc_args ) {
        $command = sprintf(
            '/usr/bin/env %s%s %s',
            Utils\get_sql_check_command(),
            $this->get_defaults_flag_string( $assoc_args ),
            '%s'
        );
        WP_CLI::debug( "Running shell command: {$command}", 'db' );

        $assoc_args['optimize'] = true;
        self::run(
            Utils\esc_cmd( $command, DB_NAME ),
            $assoc_args
        );

        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );
        WP_CLI::success( 'Database optimized.' );
    }

    /**
     * Repairs the database.
     *
     * Runs `mysqlcheck` utility with `--repair=true` using `DB_HOST`,
     * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * [See docs](http://dev.mysql.com/doc/refman/5.7/en/repair-table.html) for
     * more details on the `REPAIR TABLE` statement.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqlcheck. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqlcheck. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqlcheck. [Refer to mysqlcheck docs](https://dev.mysql.com/doc/en/mysqlcheck.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db repair
     *     Success: Database repaired.
     */
    public function repair( $_, $assoc_args ) {
        $command = sprintf(
            '/usr/bin/env %s%s %s',
            Utils\get_sql_check_command(),
            $this->get_defaults_flag_string( $assoc_args ),
            '%s'
        );
        WP_CLI::debug( "Running shell command: {$command}", 'db' );

        $assoc_args['repair'] = true;
        self::run(
            Utils\esc_cmd( $command, DB_NAME ),
            $assoc_args
        );

        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );
        WP_CLI::success( 'Database repaired.' );
    }

    /**
     * Opens a MySQL console using credentials from wp-config.php
     *
     * ## OPTIONS
     *
     * [--database=<database>]
     * : Use a specific database. Defaults to DB_NAME.
     *
     * [--default-character-set=<character-set>]
     * : Use a specific character set. Defaults to DB_CHARSET when defined.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysql. [Refer to mysql docs](https://dev.mysql.com/doc/en/mysql-command-options.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Open MySQL console
     *     $ wp db cli
     *     mysql>
     *
     * @alias connect
     */
    public function cli( $_, $assoc_args ) {

        $command = sprintf(
            '/usr/bin/env %s%s --no-auto-rehash',
            $this->get_mysql_command(),
            $this->get_defaults_flag_string( $assoc_args )
        );
        WP_CLI::debug( "Running shell command: {$command}", 'db' );

        if ( ! isset( $assoc_args['database'] ) ) {
            $assoc_args['database'] = DB_NAME;
        }

        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );
        self::run( $command, $assoc_args, null, true );
    }

    /**
     * Executes a SQL query against the database.
     *
     * Executes an arbitrary SQL query using `DB_HOST`, `DB_NAME`, `DB_USER`
     *  and `DB_PASSWORD` database credentials specified in wp-config.php.
     *
     * Use the `--skip-column-names` MySQL argument to exclude the headers
     * from a SELECT query. Pipe the output to remove the ASCII table
     * entirely.
     *
     * ## OPTIONS
     *
     * [<sql>]
     * : A SQL query. If not passed, will try to read from STDIN.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysql. [Refer to mysql docs](https://dev.mysql.com/doc/en/mysql-command-options.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Execute a query stored in a file
     *     $ wp db query < debug.sql
     *
     *     # Query for a specific value in the database (pipe the result to remove the ASCII table borders)
     *     $ wp db query 'SELECT option_value FROM wp_options WHERE option_name="home"' --skip-column-names
     *     +---------------------+
     *     | https://example.com |
     *     +---------------------+
     *
     *     # Check all tables in the database
     *     $ wp db query "CHECK TABLE $(wp db tables | paste -s -d, -);"
     *     +---------------------------------------+-------+----------+----------+
     *     | Table                                 | Op    | Msg_type | Msg_text |
     *     +---------------------------------------+-------+----------+----------+
     *     | wordpress_dbase.wp_users              | check | status   | OK       |
     *     | wordpress_dbase.wp_usermeta           | check | status   | OK       |
     *     | wordpress_dbase.wp_posts              | check | status   | OK       |
     *     | wordpress_dbase.wp_comments           | check | status   | OK       |
     *     | wordpress_dbase.wp_links              | check | status   | OK       |
     *     | wordpress_dbase.wp_options            | check | status   | OK       |
     *     | wordpress_dbase.wp_postmeta           | check | status   | OK       |
     *     | wordpress_dbase.wp_terms              | check | status   | OK       |
     *     | wordpress_dbase.wp_term_taxonomy      | check | status   | OK       |
     *     | wordpress_dbase.wp_term_relationships | check | status   | OK       |
     *     | wordpress_dbase.wp_termmeta           | check | status   | OK       |
     *     | wordpress_dbase.wp_commentmeta        | check | status   | OK       |
     *     +---------------------------------------+-------+----------+----------+
     *
     *     # Pass extra arguments through to MySQL
     *     $ wp db query 'SELECT * FROM wp_options WHERE option_name="home"' --skip-column-names
     *     +---+------+------------------------------+-----+
     *     | 2 | home | http://wordpress-develop.dev | yes |
     *     +---+------+------------------------------+-----+
     *
     * ## MULTISITE USAGE
     *
     * Please note that the global `--url` parameter will have no effect on this command.
     * In order to query for data in a site other than your primary site,
     * you will need to manually modify the table names to use the prefix that includes the site's ID.
     *
     * For example, to get the `home` option for your second site, modify the example above like so:
     *
     *     $ wp db query 'SELECT option_value FROM wp_2_options WHERE option_name="home"' --skip-column-names
     *     +----------------------+
     *     | https://example2.com |
     *     +----------------------+
     *
     * To confirm the ID for the site you want to query, you can use the `wp site list` command:
     *
     *     # wp site list --fields=blog_id,url
     *     +---------+-----------------------+
     *     | blog_id | url                   |
     *     +---------+-----------------------+
     *     | 1       | https://example1.com/ |
     *     | 2       | https://example2.com/ |
     *     +---------+-----------------------+
     */
    public function query( $args, $assoc_args ) {

        $command = sprintf(
            '/usr/bin/env %s%s --no-auto-rehash',
            $this->get_mysql_command(),
            $this->get_defaults_flag_string( $assoc_args )
        );
        WP_CLI::debug( "Running shell command: {$command}", 'db' );

        $assoc_args['database'] = DB_NAME;

        // The query might come from STDIN.
        if ( ! empty( $args ) ) {
            $assoc_args['execute'] = $args[0];
        }

        if ( isset( $assoc_args['execute'] ) ) {
            // Ensure that the SQL mode is compatible with WPDB.
            $assoc_args['execute'] = $this->get_sql_mode_query( $assoc_args ) . $assoc_args['execute'];
        }

        $is_row_modifying_query = isset( $assoc_args['execute'] ) && preg_match( '/\b(UPDATE|DELETE|INSERT|REPLACE|LOAD DATA)\b/i', $assoc_args['execute'] );

        if ( $is_row_modifying_query ) {
            $assoc_args['execute'] .= '; SELECT ROW_COUNT();';
        }

        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );
        list( $stdout, $stderr, $exit_code ) = self::run( $command, $assoc_args, false );

        if ( $exit_code ) {
            WP_CLI::error( "Query failed: {$stderr}" );
        }

        if ( $is_row_modifying_query ) {
            $output_lines  = explode( "\n", trim( $stdout ) );
            $affected_rows = (int) trim( end( $output_lines ) );
            WP_CLI::success( "Query succeeded. Rows affected: {$affected_rows}" );
        } elseif ( ! empty( $stdout ) ) {
            WP_CLI::line( $stdout );
        }
    }

    /**
     * Exports the database to a file or to STDOUT.
     *
     * Runs `mysqldump` utility using `DB_HOST`, `DB_NAME`, `DB_USER` and
     * `DB_PASSWORD` database credentials specified in wp-config.php. Accepts any valid `mysqldump` flags.
     *
     * ## OPTIONS
     *
     * [<file>]
     * : The name of the SQL file to export. If '-', then outputs to STDOUT. If
     * omitted, it will be '{dbname}-{Y-m-d}-{random-hash}.sql'.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqldump. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqldump. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqldump. [Refer to mysqldump docs](https://dev.mysql.com/doc/en/mysqldump.html#mysqldump-option-summary).
     *
     * [--tables=<tables>]
     * : The comma separated list of specific tables to export. Excluding this parameter will export all tables in the database.
     *
     * [--exclude_tables=<tables>]
     * : The comma separated list of specific tables that should be skipped from exporting. Excluding this parameter will export all tables in the database.
     *
     * [--include-tablespaces]
     * : Skips adding the default --no-tablespaces option to mysqldump.
     *
     * [--porcelain]
     * : Output filename for the exported database.
     *
     * [--add-drop-table]
     * : Include a `DROP TABLE IF EXISTS` statement before each `CREATE TABLE` statement.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Export database with drop query included
     *     $ wp db export --add-drop-table
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export certain tables
     *     $ wp db export --tables=wp_options,wp_users
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export all tables matching a wildcard
     *     $ wp db export --tables=$(wp db tables 'wp_user*' --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export all tables matching prefix
     *     $ wp db export --tables=$(wp db tables --all-tables-with-prefix --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export certain posts without create table statements
     *     $ wp db export --no-create-info=true --tables=wp_posts --where="ID in (100,101,102)"
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export relating meta for certain posts without create table statements
     *     $ wp db export --no-create-info=true --tables=wp_postmeta --where="post_id in (100,101,102)"
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Skip certain tables from the exported database
     *     $ wp db export --exclude_tables=wp_options,wp_users
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Skip all tables matching a wildcard from the exported database
     *     $ wp db export --exclude_tables=$(wp db tables 'wp_user*' --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Skip all tables matching prefix from the exported database
     *     $ wp db export --exclude_tables=$(wp db tables --all-tables-with-prefix --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export database to STDOUT.
     *     $ wp db export -
     *     -- MySQL dump 10.13  Distrib 5.7.19, for osx10.12 (x86_64)
     *     --
     *     -- Host: localhost    Database: wpdev
     *     -- ------------------------------------------------------
     *     -- Server version    5.7.19
     *     ...
     *
     * @alias dump
     */
    public function export( $args, $assoc_args ) {
        if ( ! empty( $args[0] ) ) {
            $result_file = $args[0];
        } else {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- WordPress is not loaded.
            $hash        = substr( md5( mt_rand() ), 0, 7 );
            $result_file = sprintf( '%s-%s-%s.sql', DB_NAME, date( 'Y-m-d' ), $hash ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date

        }
        $stdout    = ( '-' === $result_file );
        $porcelain = Utils\get_flag_value( $assoc_args, 'porcelain' );

        // Bail if both porcelain and STDOUT are set.
        if ( $stdout && $porcelain ) {
            WP_CLI::error( 'Porcelain is not allowed when output mode is STDOUT.' );
        }

        if ( ! $stdout ) {
            $assoc_args['result-file'] = $result_file;
        }

        $mysqldump_binary = Utils\force_env_on_nix_systems( Utils\get_sql_dump_command() );

        $support_column_statistics = exec( $mysqldump_binary . ' --help | grep "column-statistics"' );

        /*
         * In case that `--default-character-set` is not given and `DB_CHARSET` is `utf8`,
         * we try to deduce what the actual character set for the posts table of the
         * current database is and use `utf8mb4` as a `default-character-set` if that
         * seems like the safer default, to ensure emojis are encoded correctly.
         */
        if (
            ! isset( $assoc_args['default-character-set'] )
            &&
            ( defined( 'DB_CHARSET' ) && self::ENCODING_UTF8 === constant( 'DB_CHARSET' ) )
            &&
            self::ENCODING_UTF8MB4 === $this->get_posts_table_charset( $assoc_args )
        ) {
            WP_CLI::debug( 'Setting missing default character set to ' . self::ENCODING_UTF8MB4, 'db' );
            $assoc_args['default-character-set'] = self::ENCODING_UTF8MB4;
        }

        $initial_command = sprintf( "{$mysqldump_binary}%s ", $this->get_defaults_flag_string( $assoc_args ) );
        WP_CLI::debug( "Running initial shell command: {$initial_command}", 'db' );

        $default_arguments = [ '%s' ];

        if ( $support_column_statistics ) {
            $default_arguments[] = '--skip-column-statistics';
        }

        if ( ! Utils\get_flag_value( $assoc_args, 'include-tablespaces', false ) ) {
            $default_arguments[] = '--no-tablespaces';
        }

        $command = $initial_command . implode( ' ', $default_arguments );

        $command_esc_args = [ DB_NAME ];

        if ( isset( $assoc_args['tables'] ) ) {
            $tables = explode( ',', trim( $assoc_args['tables'], ',' ) );
            unset( $assoc_args['tables'] );
            $command .= ' --tables';
            foreach ( $tables as $table ) {
                $command           .= ' %s';
                $command_esc_args[] = trim( $table );
            }
        }

        $exclude_tables = Utils\get_flag_value( $assoc_args, 'exclude_tables' );
        if ( isset( $exclude_tables ) ) {
            $tables = explode( ',', trim( $assoc_args['exclude_tables'], ',' ) );
            unset( $assoc_args['exclude_tables'] );
            foreach ( $tables as $table ) {
                $command           .= ' --ignore-table';
                $command           .= ' %s';
                $command_esc_args[] = trim( DB_NAME . '.' . $table );
            }
        }

        $escaped_command = call_user_func_array( '\WP_CLI\Utils\esc_cmd', array_merge( [ $command ], $command_esc_args ) );

        // Remove parameters not needed for SQL run.
        unset( $assoc_args['porcelain'] );

        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );
        self::run( $escaped_command, $assoc_args );

        if ( $porcelain ) {
            WP_CLI::line( $result_file );
        } elseif ( ! $stdout ) {
            WP_CLI::success( sprintf( "Exported to '%s'.", $result_file ) );
        }
    }

    /**
     * Get the current character set of the posts table.
     *
     * @param array Associative array of associative arguments.
     * @return string Posts table character set.
     */
    private function get_posts_table_charset( $assoc_args ) {
        $query = 'SELECT CCSA.character_set_name '
                . 'FROM information_schema.`TABLES` T, '
                . 'information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA '
                . 'WHERE CCSA.collation_name = T.table_collation '
                . "AND T.table_schema = '" . DB_NAME . "' "
                . "AND T.table_name LIKE '%\_posts';";

        list( $stdout, $stderr, $exit_code ) = self::run(
            sprintf(
                '%s%s --no-auto-rehash --batch --skip-column-names',
                $this->get_mysql_command(),
                $this->get_defaults_flag_string( $assoc_args )
            ),
            [ 'execute' => $query ],
            false
        );

        if ( $exit_code ) {
            WP_CLI::warning(
                'Failed to get current character set of the posts table.'
                . ( ! empty( $stderr ) ? " Reason: {$stderr}" : '' )
            );

            return self::ENCODING_UTF8MB4;
        }

        $stdout = trim( $stdout );

        WP_CLI::debug( "Detected character set of the posts table: {$stdout}.", 'db' );

        return $stdout;
    }

    /**
     * Imports a database from a file or from STDIN.
     *
     * Runs SQL queries using `DB_HOST`, `DB_NAME`, `DB_USER` and
     * `DB_PASSWORD` database credentials specified in wp-config.php. This
     * does not create database by itself and only performs whatever tasks are
     * defined in the SQL.
     *
     * ## OPTIONS
     *
     * [<file>]
     * : The name of the SQL file to import. If '-', then reads from STDIN. If omitted, it will look for '{dbname}.sql'.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysql. [Refer to mysql binary docs](https://dev.mysql.com/doc/refman/8.0/en/mysql-command-options.html).
     *
     * [--skip-optimization]
     * : When using an SQL file, do not include speed optimization such as disabling auto-commit and key checks.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Import MySQL from a file.
     *     $ wp db import wordpress_dbase.sql
     *     Success: Imported from 'wordpress_dbase.sql'.
     */
    public function import( $args, $assoc_args ) {
        if ( ! empty( $args[0] ) ) {
            $result_file = $args[0];
        } else {
            $result_file = sprintf( '%s.sql', DB_NAME );
        }

        // Process options to MySQL.
        $mysql_args = array_merge(
            [ 'database' => DB_NAME ],
            self::get_dbuser_dbpass_args( $assoc_args ),
            self::get_mysql_args( $assoc_args )
        );

        if ( '-' !== $result_file ) {
            if ( ! is_readable( $result_file ) ) {
                WP_CLI::error( sprintf( 'Import file missing or not readable: %s', $result_file ) );
            }

            $query = Utils\get_flag_value( $assoc_args, 'skip-optimization' )
                ? 'SOURCE %s;'
                : 'SET autocommit = 0; SET unique_checks = 0; SET foreign_key_checks = 0; SOURCE %s; COMMIT;';

            $query = $this->get_sql_mode_query( $assoc_args ) . $query;

            $mysql_args['execute'] = sprintf( $query, $result_file );
        } else {
            $result_file = 'STDIN';
        }

        $command = sprintf(
            '/usr/bin/env %s%s --no-auto-rehash',
            $this->get_mysql_command(),
            $this->get_defaults_flag_string( $assoc_args )
        );
        WP_CLI::debug( "Running shell command: {$command}", 'db' );
        WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' );

        self::run( $command, $mysql_args );

        WP_CLI::success( sprintf( "Imported from '%s'.", $result_file ) );
    }

    /**
     * Lists the database tables.
     *
     * Defaults to all tables registered to the $wpdb database handler.
     *
     * ## OPTIONS
     *
     * [<table>...]
     * : List tables based on wildcard search, e.g. 'wp_*_options' or 'wp_post?'.
     *
     * [--scope=<scope>]
     * : Can be all, global, ms_global, blog, or old tables. Defaults to all.
     *
     * [--network]
     * : List all the tables in a multisite install.
     *
     * [--all-tables-with-prefix]
     * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network.
     *
     * [--all-tables]
     * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: list
     * options:
     *   - list
     *   - csv
     * ---
     *
     * ## EXAMPLES
     *
     *     # List tables for a single site, without shared tables like 'wp_users'
     *     $ wp db tables --scope=blog --url=sub.example.com
     *     wp_3_posts
     *     wp_3_comments
     *     wp_3_options
     *     wp_3_postmeta
     *     wp_3_terms
     *     wp_3_term_taxonomy
     *     wp_3_term_relationships
     *     wp_3_termmeta
     *     wp_3_commentmeta
     *
     *     # Export only tables for a single site
     *     $ wp db export --tables=$(wp db tables --url=sub.example.com --format=csv)
     *     Success: Exported to wordpress_dbase.sql
     *
     * @when after_wp_load
     */
    public function tables( $args, $assoc_args ) {

        $format = Utils\get_flag_value( $assoc_args, 'format' );
        unset( $assoc_args['format'] );

        if ( empty( $args ) && empty( $assoc_args ) ) {
            $assoc_args['scope'] = 'all';
        }

        $tables = Utils\wp_get_table_names( $args, $assoc_args );

        if ( 'csv' === $format ) {
            WP_CLI::line( implode( ',', $tables ) );
        } else {
            foreach ( $tables as $table ) {
                WP_CLI::line( $table );
            }
        }
    }

    /**
     * Displays the database name and size.
     *
     * Display the database name and size for `DB_NAME` specified in wp-config.php.
     * The size defaults to a human-readable number.
     *
     * Available size formats include:
     * * b (bytes)
     * * kb (kilobytes)
     * * mb (megabytes)
     * * gb (gigabytes)
     * * tb (terabytes)
     * * B   (ISO Byte setting, with no conversion)
     * * KB  (ISO Kilobyte setting, with 1 KB  = 1,000 B)
     * * KiB (ISO Kibibyte setting, with 1 KiB = 1,024 B)
     * * MB  (ISO Megabyte setting, with 1 MB  = 1,000 KB)
     * * MiB (ISO Mebibyte setting, with 1 MiB = 1,024 KiB)
     * * GB  (ISO Gigabyte setting, with 1 GB  = 1,000 MB)
     * * GiB (ISO Gibibyte setting, with 1 GiB = 1,024 MiB)
     * * TB  (ISO Terabyte setting, with 1 TB  = 1,000 GB)
     * * TiB (ISO Tebibyte setting, with 1 TiB = 1,024 GiB)
     *
     * ## OPTIONS
     *
     * [--size_format=<format>]
     * : Display the database size only, as a bare number.
     * ---
     * options:
     *   - b
     *   - kb
     *   - mb
     *   - gb
     *   - tb
     *   - B
     *   - KB
     *   - KiB
     *   - MB
     *   - MiB
     *   - GB
     *   - GiB
     *   - TB
     *   - TiB
     * ---
     *
     * [--tables]
     * : Display each table name and size instead of the database size.
     *
     * [--human-readable]
     * : Display database sizes in human readable formats.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * [--scope=<scope>]
     * : Can be all, global, ms_global, blog, or old tables. Defaults to all.
     *
     * [--network]
     * : List all the tables in a multisite install.
     *
     * [--decimals=<decimals>]
     * : Number of digits after decimal point. Defaults to 0.
     *
     * [--all-tables-with-prefix]
     * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network.
     *
     * [--all-tables]
     * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix.
     *
     * [--order=<order>]
     * : Ascending or Descending order.
     * ---
     * default: asc
     * options:
     *   - asc
     *   - desc
     * ---
     *
     * [--orderby=<orderby>]
     * : Order by fields.
     * ---
     * default: name
     * options:
     *   - name
     *   - size
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp db size
     *     +-------------------+------+
     *     | Name              | Size |
     *     +-------------------+------+
     *     | wordpress_default | 6 MB |
     *     +-------------------+------+
     *
     *     $ wp db size --tables
     *     +-----------------------+-------+
     *     | Name                  | Size  |
     *     +-----------------------+-------+
     *     | wp_users              | 64 KB |
     *     | wp_usermeta           | 48 KB |
     *     | wp_posts              | 80 KB |
     *     | wp_comments           | 96 KB |
     *     | wp_links              | 32 KB |
     *     | wp_options            | 32 KB |
     *     | wp_postmeta           | 48 KB |
     *     | wp_terms              | 48 KB |
     *     | wp_term_taxonomy      | 48 KB |
     *     | wp_term_relationships | 32 KB |
     *     | wp_termmeta           | 48 KB |
     *     | wp_commentmeta        | 48 KB |
     *     +-----------------------+-------+
     *
     *     $ wp db size --size_format=b
     *     5865472
     *
     *     $ wp db size --size_format=kb
     *     5728
     *
     *     $ wp db size --size_format=mb
     *     6
     *
     * @when after_wp_load
     */
    public function size( $args, $assoc_args ) {
        global $wpdb;

        $format                 = Utils\get_flag_value( $assoc_args, 'format' );
        $size_format            = Utils\get_flag_value( $assoc_args, 'size_format' );
        $human_readable         = Utils\get_flag_value( $assoc_args, 'human-readable', false );
        $tables                 = Utils\get_flag_value( $assoc_args, 'tables' );
        $tables                 = ! empty( $tables );
        $all_tables             = Utils\get_flag_value( $assoc_args, 'all-tables' );
        $all_tables_with_prefix = Utils\get_flag_value( $assoc_args, 'all-tables-with-prefix' );
        $order                  = Utils\get_flag_value( $assoc_args, 'order', 'asc' );
        $orderby                = Utils\get_flag_value( $assoc_args, 'orderby', null );

        if ( ! is_null( $size_format ) && $human_readable ) {
            WP_CLI::error( 'Cannot use --size_format and --human-readable arguments at the same time.' );
        }

        unset( $assoc_args['format'] );
        unset( $assoc_args['size_format'] );
        unset( $assoc_args['human-readable'] );
        unset( $assoc_args['tables'] );

        if ( empty( $args ) && empty( $assoc_args ) ) {
            $assoc_args['scope'] = 'all';
        }

        // Build rows for the formatter.
        $rows   = [];
        $fields = [ 'Name', 'Size' ];

        $default_unit = ( empty( $size_format ) && ! $human_readable ) ? ' B' : '';

        if ( $tables || $all_tables || $all_tables_with_prefix ) {

            // Add all of the table sizes.
            foreach ( Utils\wp_get_table_names( $args, $assoc_args ) as $table_name ) {

                // Get the table size.
                $table_bytes = $wpdb->get_var(
                    $wpdb->prepare(
                        'SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = %s and Table_Name = %s GROUP BY Table_Name LIMIT 1',
                        DB_NAME,
                        $table_name
                    )
                );

                // Add the table size to the list.
                $rows[] = [
                    'Name'  => $table_name,
                    'Size'  => strtoupper( $table_bytes ) . $default_unit,
                    'Bytes' => strtoupper( $table_bytes ),
                ];
            }
        } else {

            // Get the database size.
            $db_bytes = $wpdb->get_var(
                $wpdb->prepare(
                    'SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = %s GROUP BY table_schema;',
                    DB_NAME
                )
            );

            // Add the database size to the list.
            $rows[] = [
                'Name'  => DB_NAME,
                'Size'  => strtoupper( $db_bytes ) . $default_unit,
                'Bytes' => strtoupper( $db_bytes ),
            ];
        }

        if ( ! empty( $size_format ) || $human_readable ) {
            foreach ( $rows as $index => $row ) {
                // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Backfilling WP native constants.
                if ( ! defined( 'KB_IN_BYTES' ) ) {
                    define( 'KB_IN_BYTES', 1024 );
                }
                if ( ! defined( 'MB_IN_BYTES' ) ) {
                    define( 'MB_IN_BYTES', 1024 * KB_IN_BYTES );
                }
                if ( ! defined( 'GB_IN_BYTES' ) ) {
                    define( 'GB_IN_BYTES', 1024 * MB_IN_BYTES );
                }
                if ( ! defined( 'TB_IN_BYTES' ) ) {
                    define( 'TB_IN_BYTES', 1024 * GB_IN_BYTES );
                }
                // phpcs:enable

                if ( $human_readable ) {
                    $size_key = floor( log( $row['Size'] ) / log( 1000 ) );
                    $sizes    = [ 'B', 'KB', 'MB', 'GB', 'TB' ];

                    $size_format = isset( $sizes[ $size_key ] ) ? $sizes[ $size_key ] : $sizes[0];
                }

                // Display the database size as a number.
                switch ( $size_format ) {
                    case 'TB':
                        $divisor = pow( 1000, 4 );
                        break;

                    case 'GB':
                        $divisor = pow( 1000, 3 );
                        break;

                    case 'MB':
                        $divisor = pow( 1000, 2 );
                        break;

                    case 'KB':
                        $divisor = 1000;
                        break;

                    case 'tb':
                    case 'TiB':
                        $divisor = TB_IN_BYTES;
                        break;

                    case 'gb':
                    case 'GiB':
                        $divisor = GB_IN_BYTES;
                        break;

                    case 'mb':
                    case 'MiB':
                        $divisor = MB_IN_BYTES;
                        break;

                    case 'kb':
                    case 'KiB':
                        $divisor = KB_IN_BYTES;
                        break;

                    case 'b':
                    case 'B':
                    default:
                        $divisor = 1;
                        break;
                }
                    $size_format_display = preg_replace( '/IB$/u', 'iB', strtoupper( $size_format ) );

                    $decimals               = Utils\get_flag_value( $assoc_args, 'decimals', 0 );
                    $rows[ $index ]['Size'] = round( (int) $row['Bytes'] / $divisor, $decimals ) . ' ' . $size_format_display;
            }
        }

        if ( ! empty( $size_format ) && ! $tables && ! $format && ! $human_readable && true !== $all_tables && true !== $all_tables_with_prefix ) {
            WP_CLI::line( str_replace( " {$size_format_display}", '', $rows[0]['Size'] ) );
        } else {

            // Sort the rows by user input
            if ( $orderby ) {
                usort(
                    $rows,
                    function ( $a, $b ) use ( $order, $orderby ) {

                        $orderby_array          = 'asc' === $order ? array( $a, $b ) : array( $b, $a );
                        list( $first, $second ) = $orderby_array;

                        if ( 'size' === $orderby ) {
                            return $first['Bytes'] > $second['Bytes'];
                        }

                        return strcmp( $first['Name'], $second['Name'] );
                    }
                );
            }

            // Display the rows.
            $args = [
                'format' => $format,
            ];

            $formatter = new Formatter( $args, $fields );
            $formatter->display_items( $rows );
        }
    }

    /**
     * Displays the database table prefix.
     *
     * Display the database table prefix, as defined by the database handler's interpretation of the current site.
     *
     * ## EXAMPLES
     *
     *     $ wp db prefix
     *     wp_
     *
     * @when after_wp_load
     */
    public function prefix() {
        global $wpdb;

        WP_CLI::log( $wpdb->prefix );
    }

    /**
     * Finds a string in the database.
     *
     * Searches through all of the text columns in a selection of database tables for a given string, Outputs colorized references to the string.
     *
     * Defaults to searching through all tables registered to $wpdb. On multisite, this default is limited to the tables for the current site.
     *
     * ## OPTIONS
     *
     * <search>
     * : String to search for. The search is case-insensitive by default.
     *
     * [<tables>...]
     * : One or more tables to search through for the string.
     *
     * [--network]
     * : Search through all the tables registered to $wpdb in a multisite install.
     *
     * [--all-tables-with-prefix]
     * : Search through all tables that match the registered table prefix, even if not registered on $wpdb. On one hand, sometimes plugins use tables without registering them to $wpdb. On another hand, this could return tables you don't expect. Overrides --network.
     *
     * [--all-tables]
     * : Search through ALL tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --network and --all-tables-with-prefix.
     *
     * [--before_context=<num>]
     * : Number of characters to display before the match.
     * ---
     * default: 40
     * ---
     *
     * [--after_context=<num>]
     * : Number of characters to display after the match.
     * ---
     * default: 40
     * ---
     *
     * [--regex]
     * : Runs the search as a regular expression (without delimiters). The search becomes case-sensitive (i.e. no PCRE flags are added). Delimiters must be escaped if they occur in the expression. Because the search is run on individual columns, you can use the `^` and `$` tokens to mark the start and end of a match, respectively.
     *
     * [--regex-flags=<regex-flags>]
     * : Pass PCRE modifiers to the regex search (e.g. 'i' for case-insensitivity).
     *
     * [--regex-delimiter=<regex-delimiter>]
     * : The delimiter to use for the regex. It must be escaped if it appears in the search string. The default value is the result of `chr(1)`.
     *
     * [--table_column_once]
     * : Output the 'table:column' line once before all matching row lines in the table column rather than before each matching row.
     *
     * [--one_line]
     * : Place the 'table:column' output on the same line as the row id and match ('table:column:id:match'). Overrides --table_column_once.
     *
     * [--matches_only]
     * : Only output the string matches (including context). No 'table:column's or row ids are outputted.
     *
     * [--stats]
     * : Output stats on the number of matches found, time taken, tables/columns/rows searched, tables skipped.
     *
     * [--table_column_color=<color_code>]
     * : Percent color code to use for the 'table:column' output. For a list of available percent color codes, see below. Default '%G' (bright green).
     *
     * [--id_color=<color_code>]
     * : Percent color code to use for the row id output. For a list of available percent color codes, see below. Default '%Y' (bright yellow).
     *
     * [--match_color=<color_code>]
     * : Percent color code to use for the match (unless both before and after context are 0, when no color code is used). For a list of available percent color codes, see below. Default '%3%k' (black on a mustard background).
     *
     * [--fields=<fields>]
     * : Get a specific subset of the fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     *   - ids
     *   - count
     * ---
     *
     * The percent color codes available are:
     *
     * | Code | Color
     * | ---- | -----
     * |  %y  | Yellow (dark) (mustard)
     * |  %g  | Green (dark)
     * |  %b  | Blue (dark)
     * |  %r  | Red (dark)
     * |  %m  | Magenta (dark)
     * |  %c  | Cyan (dark)
     * |  %w  | White (dark) (light gray)
     * |  %k  | Black
     * |  %Y  | Yellow (bright)
     * |  %G  | Green (bright)
     * |  %B  | Blue (bright)
     * |  %R  | Red (bright)
     * |  %M  | Magenta (bright)
     * |  %C  | Cyan (bright)
     * |  %W  | White
     * |  %K  | Black (bright) (dark gray)
     * |  %3  | Yellow background (dark) (mustard)
     * |  %2  | Green background (dark)
     * |  %4  | Blue background (dark)
     * |  %1  | Red background (dark)
     * |  %5  | Magenta background (dark)
     * |  %6  | Cyan background (dark)
     * |  %7  | White background (dark) (light gray)
     * |  %0  | Black background
     * |  %8  | Reverse
     * |  %U  | Underline
     * |  %F  | Blink (unlikely to work)
     *
     * They can be concatenated. For instance, the default match color of black on a mustard (dark yellow) background `%3%k` can be made black on a bright yellow background with `%Y%0%8`.
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each result:
     *
     * * table
     * * column
     * * match
     * * primary_key_name
     * * primary_key_value
     *
     * ## EXAMPLES
     *
     *     # Search through the database for the 'wordpress-develop' string
     *     $ wp db search wordpress-develop
     *     wp_options:option_value
     *     1:http://wordpress-develop.dev
     *     wp_options:option_value
     *     1:https://example.com/foo
     *         ...
     *
     *     # Search through a multisite database on the subsite 'foo' for the 'example.com' string
     *     $ wp db search example.com --url=example.com/foo
     *     wp_2_comments:comment_author_url
     *     1:https://example.com/
     *     wp_2_options:option_value
     *         ...
     *
     *     # Search through the database for the 'https?://' regular expression, printing stats.
     *     $ wp db search 'https?://' --regex --stats
     *     wp_comments:comment_author_url
     *     1:https://wordpress.org/
     *         ...
     *     Success: Found 99146 matches in 10.752s (10.559s searching). Searched 12 tables, 53 columns, 1358907 rows. 1 table skipped: wp_term_relationships.
     *
     *     # SQL search database table 'wp_options' where 'option_name' match 'foo'
     *     wp db query 'SELECT * FROM wp_options WHERE option_name like "%foo%"' --skip-column-names
     *     +----+--------------+--------------------------------+-----+
     *     | 98 | foo_options  | a:1:{s:12:"_multiwidget";i:1;} | yes |
     *     | 99 | foo_settings | a:0:{}                         | yes |
     *     +----+--------------+--------------------------------+-----+
     *
     *     # SQL search and delete records from database table 'wp_options' where 'option_name' match 'foo'
     *     wp db query "DELETE from wp_options where option_id in ($(wp db query "SELECT GROUP_CONCAT(option_id SEPARATOR ',') from wp_options where option_name like '%foo%';" --silent --skip-column-names))"
     *
     *     # Search for a string and print the result as a table
     *     $ wp db search https://localhost:8889 --format=table --fields=table,column,match
     *     +------------+--------------+-----------------------------+
     *     | table      | column       | match                       |
     *     +------------+--------------+-----------------------------+
     *     | wp_options | option_value | https://localhost:8889      |
     *     | wp_options | option_value | https://localhost:8889      |
     *     | wp_posts   | guid         | https://localhost:8889/?p=1 |
     *     | wp_users   | user_url     | https://localhost:8889      |
     *     +------------+--------------+-----------------------------+
     *
     *     # Search for a string and get only the IDs (only works for a single table)
     *     $ wp db search https://localhost:8889 wp_options --format=ids
     *     1 2
     *
     * @when after_wp_load
     */
    public function search( $args, $assoc_args ) {
        global $wpdb;

        $start_run_time = microtime( true );

        $search = array_shift( $args );

        $before_context = Utils\get_flag_value( $assoc_args, 'before_context', 40 );
        $before_context = '' === $before_context ? $before_context : (int) $before_context;

        $after_context = Utils\get_flag_value( $assoc_args, 'after_context', 40 );
        $after_context = '' === $after_context ? $after_context : (int) $after_context;

        $regex = Utils\get_flag_value( $assoc_args, 'regex', false );
        if ( false !== $regex ) {
            $regex_flags             = Utils\get_flag_value( $assoc_args, 'regex-flags', false );
            $default_regex_delimiter = false;
            $regex_delimiter         = Utils\get_flag_value( $assoc_args, 'regex-delimiter', '' );
            if ( '' === $regex_delimiter ) {
                $regex_delimiter         = chr( 1 );
                $default_regex_delimiter = true;
            }
        }

        $colors = self::get_colors(
            $assoc_args,
            [
                'table_column' => '%G',
                'id'           => '%Y',
                'match'        => $before_context || $after_context ? '%3%k' : '',
            ]
        );

        $table_column_once = Utils\get_flag_value( $assoc_args, 'table_column_once', false );
        $one_line          = Utils\get_flag_value( $assoc_args, 'one_line', false );
        $matches_only      = Utils\get_flag_value( $assoc_args, 'matches_only', false );
        $stats             = Utils\get_flag_value( $assoc_args, 'stats', false );
        $fields            = Utils\get_flag_value( $assoc_args, 'fields' );
        $format            = Utils\get_flag_value( $assoc_args, 'format' );

        $column_count = 0;
        $row_count    = 0;
        $match_count  = 0;
        $skipped      = [];

        if ( $regex ) {
            // Note the user must escape the delimiter in the search.
            $search_regex = $regex_delimiter . $search . $regex_delimiter;
            if ( $regex_flags ) {
                $search_regex .= $regex_flags;
            }
            // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Unsure why this is needed, leaving in for now.
            if ( false === @preg_match( $search_regex, '' ) ) {
                if ( $default_regex_delimiter ) {
                    $flags_msg = $regex_flags ? "flags '$regex_flags'" : 'no flags';
                    $msg       = "The regex pattern '$search' with default delimiter 'chr(1)' and {$flags_msg} fails.";
                } else {
                    $msg = "The regex '$search_regex' fails.";
                }
                WP_CLI::error( $msg );
            }
        } else {
            $search_regex    = '#' . preg_quote( $search, '#' ) . '#i';
            $esc_like_search = '%' . Utils\esc_like( $search ) . '%';
        }

        $encoding = null;
        if ( 0 === strpos( $wpdb->charset, self::ENCODING_UTF8 ) ) {
            $encoding = 'UTF-8';
        }

        $tables = Utils\wp_get_table_names( $args, $assoc_args );

        $search_results = [];

        $start_search_time = microtime( true );

        foreach ( $tables as $table ) {
            list( $primary_keys, $text_columns, $all_columns ) = self::get_columns( $table );
            if ( ! $all_columns ) {
                WP_CLI::error( "No such table '$table'." );
            }
            if ( ! $text_columns ) {
                if ( $stats ) {
                    $skipped[] = $table;
                    // Don't bother warning for term relationships (which is just 3 int columns).
                } elseif ( ! preg_match( '/_term_relationships$/', $table ) ) {
                    WP_CLI::warning( $primary_keys ? "No text columns for table '$table' - skipped." : "No primary key or text columns for table '$table' - skipped." );
                }
                continue;
            }
            $table_sql     = self::esc_sql_ident( $table );
            $column_count += count( $text_columns );
            if ( ! $primary_keys ) {
                WP_CLI::warning( "No primary key for table '$table'. No row ids will be outputted." );
                $primary_key     = '';
                $primary_key_sql = '';
            } else {
                $primary_key     = array_shift( $primary_keys );
                $primary_key_sql = self::esc_sql_ident( $primary_key ) . ', ';
            }

            foreach ( $text_columns as $column ) {
                $column_sql = self::esc_sql_ident( $column );
                if ( $regex ) {
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped through esc_sql_ident/esc_like.
                    $results = $wpdb->get_results( "SELECT {$primary_key_sql}{$column_sql} FROM {$table_sql}" );
                } else {
                    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped through esc_sql_ident/esc_like.
                    $results = $wpdb->get_results( $wpdb->prepare( "SELECT {$primary_key_sql}{$column_sql} FROM {$table_sql} WHERE {$column_sql} LIKE %s;", $esc_like_search ) );
                }
                if ( $results ) {
                    $row_count                  += count( $results );
                    $table_column_val            = $colors['table_column'][0] . "{$table}:{$column}" . $colors['table_column'][1];
                    $outputted_table_column_once = false;
                    foreach ( $results as $result ) {
                        $col_val = $result->$column;
                        if ( preg_match_all( $search_regex, $col_val, $matches, PREG_OFFSET_CAPTURE ) ) {
                            if ( ! $format && ! $matches_only && ( ! $table_column_once || ! $outputted_table_column_once ) && ! $one_line ) {
                                WP_CLI::log( $table_column_val );
                                $outputted_table_column_once = true;
                            }
                            $pk_val = $primary_key ? ( $colors['id'][0] . $result->$primary_key . $colors['id'][1] . ':' ) : '';

                            $bits         = [];
                            $col_encoding = $encoding;
                            if ( ! $col_encoding && ( $before_context || $after_context ) && function_exists( 'mb_detect_encoding' ) ) {
                                $col_encoding = mb_detect_encoding( $col_val, null, true /*strict*/ );
                            }
                            $append_next = false;
                            $last_offset = 0;
                            $match_cnt   = count( $matches[0] );
                            for ( $i = 0; $i < $match_cnt; $i++ ) {
                                $match           = $matches[0][ $i ][0];
                                $offset          = $matches[0][ $i ][1];
                                $log             = $colors['match'][0] . $match . $colors['match'][1];
                                $before          = '';
                                $after           = '';
                                $after_shortened = false;

                                // Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`.
                                if ( $before_context && $offset && ! $append_next ) {
                                    $before = \cli\safe_substr( substr( $col_val, $last_offset, $offset - $last_offset ), -$before_context, null /*length*/, false /*is_width*/, $col_encoding );
                                }
                                if ( $after_context ) {
                                    $end_offset = $offset + strlen( $match );
                                    $after      = \cli\safe_substr( substr( $col_val, $end_offset ), 0, $after_context, false /*is_width*/, $col_encoding );
                                    // To lessen context duplication in output, shorten the after context if it overlaps with the next match.
                                    if ( $i + 1 < $match_cnt && $end_offset + strlen( $after ) > $matches[0][ $i + 1 ][1] ) {
                                        $after           = substr( $after, 0, $matches[0][ $i + 1 ][1] - $end_offset );
                                        $after_shortened = true;
                                        // On the next iteration, will append with no before context.
                                    }
                                }
                                if ( $append_next ) {
                                    $cnt               = count( $bits );
                                    $bits[ $cnt - 1 ] .= $log . $after;
                                } else {
                                    $bits[] = $before . $log . $after;
                                }
                                $append_next = $after_shortened;
                                $last_offset = $offset;
                            }
                            $match_count += $match_cnt;
                            $col_val      = implode( ' [...] ', $bits );

                            if ( $format ) {
                                $search_results[] = [
                                    'table'             => $table,
                                    'column'            => $column,
                                    // Remove the colors for the format output.
                                    'match'             => str_replace(
                                        [ $colors['match'][0], $colors['match'][1] ],
                                        [ '','' ],
                                        $col_val
                                    ),
                                    'primary_key_name'  => $primary_key,
                                    'primary_key_value' => $result->$primary_key,
                                ];
                            } else {
                                WP_CLI::log( $matches_only ? $col_val : ( $one_line ? "{$table_column_val}:{$pk_val}{$col_val}" : "{$pk_val}{$col_val}" ) );
                            }
                        }
                    }
                }
            }
        }

        if ( $format ) {
            $formatter_args   = [
                'format' => $format,
            ];
            $formatter_fields = [ 'table', 'column', 'match', 'primary_key_name', 'primary_key_value' ];

            if ( $fields ) {
                $fields           = explode( ',', $assoc_args['fields'] );
                $formatter_fields = array_values( array_intersect( $formatter_fields, $fields ) );
            }

            if ( in_array( $format, [ 'ids', 'count' ], true ) ) {
                if ( count( $tables ) > 1 ) {
                    WP_CLI::error( "The \"{$format}\" format can only be used for a single table." );
                }
                $search_results = array_column( $search_results, 'primary_key_value' );
            }

            $formatter = new Formatter( $formatter_args, $formatter_fields );
            $formatter->display_items( $search_results );
        }

        if ( $stats ) {
            $table_count   = count( $tables );
            $skipped_count = count( $skipped );
            $match_str     = 1 === $match_count ? 'match' : 'matches';
            $table_str     = 1 === $table_count ? 'table' : 'tables';
            $column_str    = 1 === $column_count ? 'column' : 'columns';
            $row_str       = 1 === $row_count ? 'row' : 'rows';
            $skipped_str   = 1 === $skipped_count ? 'table skipped' : 'tables skipped';
            if ( 0 !== $skipped_count ) {
                $skipped_str .= ': ' . implode( ', ', $skipped );
            }
            $end_time    = microtime( true );
            $run_time    = $end_time - $start_run_time;
            $search_time = $end_time - $start_search_time;
            $stats_msg   = sprintf(
                'Found %d %s in %.3fs (%.3fs searching). Searched %d %s, %d %s, %d %s. %d %s.',
                $match_count,
                $match_str,
                $run_time,
                $search_time,
                $table_count,
                $table_str,
                $column_count,
                $column_str,
                $row_count,
                $row_str,
                $skipped_count,
                $skipped_str
            );
            WP_CLI::success( $stats_msg );
        }
    }

    /**
     * Displays information about a given table.
     *
     * ## OPTIONS
     *
     * <table>
     * : Name of the database table.
     *
     * [--format]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp db columns wp_posts
     *     +-----------------------+---------------------+------+-----+---------------------+----------------+
     *     |         Field         |        Type         | Null | Key |       Default       |     Extra      |
     *     +-----------------------+---------------------+------+-----+---------------------+----------------+
     *     | ID                    | bigint(20) unsigned | NO   | PRI |                     | auto_increment |
     *     | post_author           | bigint(20) unsigned | NO   | MUL | 0                   |                |
     *     | post_date             | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_date_gmt         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_content          | longtext            | NO   |     |                     |                |
     *     | post_title            | text                | NO   |     |                     |                |
     *     | post_excerpt          | text                | NO   |     |                     |                |
     *     | post_status           | varchar(20)         | NO   |     | publish             |                |
     *     | comment_status        | varchar(20)         | NO   |     | open                |                |
     *     | ping_status           | varchar(20)         | NO   |     | open                |                |
     *     | post_password         | varchar(255)        | NO   |     |                     |                |
     *     | post_name             | varchar(200)        | NO   | MUL |                     |                |
     *     | to_ping               | text                | NO   |     |                     |                |
     *     | pinged                | text                | NO   |     |                     |                |
     *     | post_modified         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_modified_gmt     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_content_filtered | longtext            | NO   |     |                     |                |
     *     | post_parent           | bigint(20) unsigned | NO   | MUL | 0                   |                |
     *     | guid                  | varchar(255)        | NO   |     |                     |                |
     *     | menu_order            | int(11)             | NO   |     | 0                   |                |
     *     | post_type             | varchar(20)         | NO   | MUL | post                |                |
     *     | post_mime_type        | varchar(100)        | NO   |     |                     |                |
     *     | comment_count         | bigint(20)          | NO   |     | 0                   |                |
     *     +-----------------------+---------------------+------+-----+---------------------+----------------+
     *
     * @when after_wp_load
     */
    public function columns( $args, $assoc_args ) {
        global $wpdb;

        $format = Utils\get_flag_value( $assoc_args, 'format' );

        Utils\wp_get_table_names( [ $args[0] ], [ 'all-tables' => true ] );

        $columns = $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Asserted to be a valid table name through wp_get_table_names.
            'SHOW COLUMNS FROM ' . $args[0]
        );

        $formatter_fields = [ 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra' ];
        $formatter_args   = [
            'format' => $format,
        ];

        $formatter = new Formatter( $formatter_args, $formatter_fields );
        $formatter->display_items( $columns );
    }

    private static function get_create_query() {

        $create_query = sprintf( 'CREATE DATABASE %s', self::esc_sql_ident( DB_NAME ) );
        if ( defined( 'DB_CHARSET' ) && constant( 'DB_CHARSET' ) ) {
            $create_query .= sprintf( ' DEFAULT CHARSET %s', self::esc_sql_ident( DB_CHARSET ) );
        }
        if ( defined( 'DB_COLLATE' ) && constant( 'DB_COLLATE' ) ) {
            $create_query .= sprintf( ' DEFAULT COLLATE %s', self::esc_sql_ident( DB_COLLATE ) );
        }
        return $create_query;
    }

    /**
     * Run a single query via the 'mysql' binary.
     *
     * This includes the necessary setup to make sure the queries behave similar
     * to what WPDB produces.
     *
     * @param string $query      Query to execute.
     * @param array  $assoc_args Optional. Associative array of arguments.
     */
    protected function run_query( $query, $assoc_args = [] ) {
        // Ensure that the SQL mode is compatible with WPDB.
        $query = $this->get_sql_mode_query( $assoc_args ) . $query;

        WP_CLI::debug( "Query: {$query}", 'db' );

        $mysql_args = array_merge(
            self::get_dbuser_dbpass_args( $assoc_args ),
            self::get_mysql_args( $assoc_args )
        );

        self::run(
            sprintf(
                '%s%s --no-auto-rehash',
                $this->get_mysql_command(),
                $this->get_defaults_flag_string( $assoc_args )
            ),
            array_merge( [ 'execute' => $query ], $mysql_args )
        );
    }

    /**
     * Run a MySQL command and optionally return the output.
     *
     * @param string $cmd           Command to run.
     * @param array  $assoc_args    Optional. Associative array of arguments to
     *                              use.
     * @param bool   $send_to_shell Optional. Whether to send STDOUT and STDERR
     *                              immediately to the shell. Defaults to true.
     * @param bool   $interactive   Optional. Whether MySQL is meant to be
     *                              executed as an interactive process. Defaults
     *                              to false.
     *
     * @return array {
     *     Associative array containing STDOUT and STDERR output.
     *
     *     @type string $stdout    Output that was sent to STDOUT.
     *     @type string $stderr    Output that was sent to STDERR.
     *     @type int    $exit_code Exit code of the process.
     * }
     */
    private static function run( $cmd, $assoc_args = [], $send_to_shell = true, $interactive = false ) {
        $required = [
            'host' => DB_HOST,
            'user' => DB_USER,
            'pass' => DB_PASSWORD,
        ];

        if ( ! isset( $assoc_args['default-character-set'] )
            && defined( 'DB_CHARSET' ) && constant( 'DB_CHARSET' ) ) {
            $required['default-character-set'] = constant( 'DB_CHARSET' );
        }

        // Using 'dbuser' as option name to workaround clash with WP-CLI's global WP 'user' parameter, with 'dbpass' also available for tidyness.
        if ( isset( $assoc_args['dbuser'] ) ) {
            $required['user'] = $assoc_args['dbuser'];
            unset( $assoc_args['dbuser'] );
        }
        if ( isset( $assoc_args['dbpass'] ) ) {
            $required['pass'] = $assoc_args['dbpass'];
            unset( $assoc_args['dbpass'], $assoc_args['password'] );
        }

        $final_args = array_merge( $required, $assoc_args );

        // Adapt ordering of arguments.
        uksort(
            $final_args,
            static function ( $a, $b ) {
                if ( 'force' === $a ) {
                    return 1;
                } elseif ( 'force' === $b ) {
                    return -1;
                } else {
                    return 0;
                }
            }
        );

        return Utils\run_mysql_command( $cmd, $final_args, null, $send_to_shell, $interactive );
    }

    /**
     * Helper to pluck 'dbuser' and 'dbpass' from associative args array.
     *
     * @param array $assoc_args Associative args array.
     * @return array Array with `dbuser' and 'dbpass' set if in passed-in associative args array.
     */
    private static function get_dbuser_dbpass_args( $assoc_args ) {
        $mysql_args = [];
        $dbuser     = Utils\get_flag_value( $assoc_args, 'dbuser' );
        if ( null !== $dbuser ) {
            $mysql_args['dbuser'] = $dbuser;
        }
        $dbpass = Utils\get_flag_value( $assoc_args, 'dbpass' );
        if ( null !== $dbpass ) {
            $mysql_args['dbpass'] = $dbpass;
        }
        return $mysql_args;
    }

    /**
     * Gets the column names of a db table differentiated into key columns and text columns and all columns.
     *
     * @param string $table The table name.
     * @return array A 3 element array consisting of an array of primary key column names, an array of text column names, and an array containing all column names.
     */
    private static function get_columns( $table ) {
        global $wpdb;

        $table_sql       = self::esc_sql_ident( $table );
        $primary_keys    = [];
        $text_columns    = [];
        $all_columns     = [];
        $suppress_errors = $wpdb->suppress_errors();

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped through esc_sql_ident/esc_like.
        $results = $wpdb->get_results( "DESCRIBE $table_sql" );
        if ( $results ) {
            foreach ( $results as $col ) {
                // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Property names come from database.
                if ( 'PRI' === $col->Key ) {
                    $primary_keys[] = $col->Field;
                }
                if ( self::is_text_col( $col->Type ) ) {
                    $text_columns[] = $col->Field;
                }
                $all_columns[] = $col->Field;
                // phpcs:enable
            }
        }
        $wpdb->suppress_errors( $suppress_errors );
        return [ $primary_keys, $text_columns, $all_columns ];
    }

    /**
     * Determines whether a column is considered text or not.
     *
     * @param string Column type.
     * @return bool True if text column, false otherwise.
     */
    private static function is_text_col( $type ) {
        foreach ( [ 'text', 'varchar' ] as $token ) {
            if ( false !== strpos( $type, $token ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
     * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
     *
     * @param string|array $idents A single identifier or an array of identifiers.
     * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
     */
    private static function esc_sql_ident( $idents ) {
        $backtick = static function ( $v ) {
            // Escape any backticks in the identifier by doubling.
            return '`' . str_replace( '`', '``', $v ) . '`';
        };
        if ( is_string( $idents ) ) {
            return $backtick( $idents );
        }
        return array_map( $backtick, $idents );
    }

    /**
     * Gets the color codes from the options if any, and returns the passed in array colorized with 2 elements per entry, a color code (or '') and a reset (or '').
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @param array $colors Array of default percent color code strings keyed by the 3 color contexts 'table_column', 'id', 'match'.
     * @return array Array containing 3 2-element arrays.
     */
    private function get_colors( $assoc_args, $colors ) {
        $color_reset = WP_CLI::colorize( '%n' );

        $color_codes = implode(
            '',
            array_map(
                function ( $v ) {
                    return substr( $v, 1 );
                },
                array_keys( \cli\Colors::getColors() )
            )
        );

        $color_codes_regex = '/^(?:%[' . $color_codes . '])*$/';

        foreach ( array_keys( $colors ) as $color_col ) {
            $col_color_flag = Utils\get_flag_value( $assoc_args, $color_col . '_color', false );
            if ( false !== $col_color_flag ) {
                if ( ! preg_match( $color_codes_regex, $col_color_flag, $matches ) ) {
                    WP_CLI::warning( "Unrecognized percent color code '$col_color_flag' for '{$color_col}_color'." );
                } else {
                    $colors[ $color_col ] = $matches[0];
                }
            }
            $colors[ $color_col ] = $colors[ $color_col ] ? [ WP_CLI::colorize( $colors[ $color_col ] ), $color_reset ] : [ '', '' ];
        }

        return $colors;
    }

    /**
     * Helper to pluck `mysql` options from associative args array.
     *
     * @param array $assoc_args Associative args array.
     * @return array Array with `mysql` options set if in passed-in associative args array.
     */
    private static function get_mysql_args( $assoc_args ) {

        $allowed_mysql_options = [
            'auto-rehash',
            'auto-vertical-output',
            'batch',
            'binary-as-hex',
            'binary-mode',
            'bind-address',
            'character-sets-dir',
            'column-names',
            'column-type-info',
            'comments',
            'compress',
            'connect-expired-password',
            'connect_timeout',
            'database',
            'debug',
            'debug-check',
            'debug-info',
            'default-auth',
            'default-character-set',
            'defaults-extra-file',
            'defaults-file',
            'defaults-group-suffix',
            'delimiter',
            'enable-cleartext-plugin',
            'execute',
            'force',
            'get-server-public-key',
            'help',
            'histignore',
            'host',
            'html',
            'ignore-spaces',
            'init-command',
            'line-numbers',
            'local-infile',
            'login-path',
            'max_allowed_packet',
            'max_join_size',
            'named-commands',
            'net_buffer_length',
            'no-beep',
            'one-database',
            'pager',
            'pipe',
            'plugin-dir',
            'port',
            'print-defaults',
            'protocol',
            'quick',
            'raw',
            'reconnect',
            'i-am-a-dummy',
            'safe-updates',
            'secure-auth',
            'select_limit',
            'server-public-key-path',
            'shared-memory-base-name',
            'show-warnings',
            'sigint-ignore',
            'silent',
            'skip-auto-rehash',
            'skip-column-names',
            'skip-line-numbers',
            'skip-named-commands',
            'skip-pager',
            'skip-reconnect',
            'socket',
            'ssl-ca',
            'ssl-capath',
            'ssl-cert',
            'ssl-cipher',
            'ssl-crl',
            'ssl-crlpath',
            'ssl-fips-mode',
            'ssl-key',
            'ssl-mode',
            'syslog',
            'table',
            'tee',
            'tls-version',
            'unbuffered',
            'verbose',
            'version',
            'vertical',
            'wait',
            'xml',
        ];

        $mysql_args = [];

        foreach ( $assoc_args as $mysql_option_key => $mysql_option_value ) {
            // Check flags to make sure they only contain valid options.
            if ( in_array( $mysql_option_key, $allowed_mysql_options, true ) && ! empty( $mysql_option_value ) ) {
                $mysql_args[ $mysql_option_key ] = $mysql_option_value;
            }
        }

        return $mysql_args;
    }

    /**
     * Writes out the `--no-defaults` flag for MySQL commands unless the --defaults flag is specified for the WP_CLI command.
     *
     * @param array $assoc_args Associative args array.
     * @return string Either the '--no-defaults' flag for use in the command or an empty string.
     */
    protected function get_defaults_flag_string( &$assoc_args ) {

        $flag_string = ' --no-defaults';

        if ( array_key_exists( 'defaults', $assoc_args ) ) {

            if ( true === Utils\get_flag_value( $assoc_args, 'defaults' ) ) {
                $flag_string = '';
            }

            unset( $assoc_args['defaults'] );

        }

        return $flag_string;
    }

    /**
     * Get the query to change the current SQL mode, and ensure its WordPress compatibility.
     *
     * If no modes are passed, it will ensure the current MySQL server modes are
     * compatible.
     *
     * Copied and adapted from WordPress Core code.
     *
     * @see https://github.com/WordPress/wordpress-develop/blob/5.4.0/src/wp-includes/wp-db.php#L817-L880
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @param array $modes      Optional. A list of SQL modes to set.
     * @return string Query string to use for setting the SQL modes to a
     *                compatible state.
     */
    protected function get_sql_mode_query( $assoc_args, $modes = [] ) {
        if ( empty( $modes ) ) {
            $modes = $this->get_current_sql_modes( $assoc_args );
        }

        $modes = array_change_key_case( $modes, CASE_UPPER );

        $is_mode_adaptation_needed = false;
        foreach ( $modes as $i => $mode ) {
            if ( in_array( $mode, $this->sql_incompatible_modes, true ) ) {
                unset( $modes[ $i ] );
                $is_mode_adaptation_needed = true;
            }
        }

        if ( ! $is_mode_adaptation_needed ) {
            WP_CLI::debug(
                sprintf(
                    'SQL modes look fine: %s',
                    json_encode( $modes )
                )
            );
            return '';
        }

        WP_CLI::debug(
            sprintf(
                'SQL mode adaptation is needed: %s => %s',
                json_encode( $this->get_current_sql_modes( $assoc_args ) ),
                json_encode( $modes )
            )
        );

        $modes_str = implode( ',', $modes );

        return "SET SESSION sql_mode='{$modes_str}';";
    }

    /**
     * Get the list of current SQL modes.
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @return string[] Array of SQL modes.
     */
    protected function get_current_sql_modes( $assoc_args ) {
        static $modes = null;

        // Make sure the provided arguments don't interfere with the expected
        // output here.
        $args = [];
        foreach ( [] as $arg ) {
            if ( isset( $assoc_args[ $arg ] ) ) {
                $args[ $arg ] = $assoc_args[ $arg ];
            }
        }

        if ( null === $modes ) {
            $modes = [];

            list( $stdout, $stderr, $exit_code ) = self::run(
                sprintf(
                    '%s%s --no-auto-rehash --batch --skip-column-names',
                    $this->get_mysql_command(),
                    $this->get_defaults_flag_string( $assoc_args )
                ),
                array_merge( $args, [ 'execute' => 'SELECT @@SESSION.sql_mode' ] ),
                false
            );

            if ( $exit_code ) {
                WP_CLI::error(
                    'Failed to get current SQL modes.'
                    . ( ! empty( $stderr ) ? " Reason: {$stderr}" : '' ),
                    $exit_code
                );
            }

            if ( ! empty( $stdout ) ) {
                $modes = array_filter(
                    array_map(
                        'trim',
                        preg_split( "/\r\n|\n|\r|,/", $stdout )
                    )
                );
            }

            if ( false === $modes ) {
                $modes = [];
            }
        }

        return $modes;
    }

    /**
     * Returns the correct `mysql` command based on the detected database type.
     *
     * @return string The appropriate check command.
     */
    private function get_mysql_command() {
        return 'mariadb' === Utils\get_db_type() ? 'mariadb' : 'mysql';
    }
}

:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ Read-Only ]

:: Make Dir ::
 
[ Read-Only ]
:: Make File ::
 
[ Read-Only ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.025 ]--