<?php

use \WP_CLI\Utils;

/**
 * Manage database prefix.
 *
 * @package wp-cli
 */
class DB_Prefix_Command extends WP_CLI_Command {

    const CODE_FAILED_TURN_ON_MAINTENANCE_MODE = 10101;
    const CODE_FAILED_TURN_OFF_MAINTENANCE_MODE = 10102;
    const CODE_FAILED_DEACTIVATE_PLUGIN = 10103;
    const CODE_FAILED_ACTIVATE_PLUGIN = 10104;
    const CODE_WRONG_PREFIX = 10105;
    const CODE_FAILED_CHANGE_WP_CONFIG = 10106;
    const CODE_FAILED_CHANGE_DB = 10107;

    /**
     * Get database prefix.
     *
     * ## EXAMPLES
     *
     *     wp db-prefix get
     */
    public function get() {
        global $wpdb;

        WP_CLI::print_value( $wpdb->base_prefix );
    }

    /**
     * Set new database prefix in database and wp-config.
     *
     * ## OPTIONS
     *
     * [<prefix>]
     * : New database prefix. If not passed, the 'wp_' is used.
     *
     * [--skip-success=<skip-success>]
     * : Skip success messages:
     *
     *    **true**: skip success messages.
     *
     * ## EXAMPLES
     *
     *     wp db-prefix set
     *     wp db-prefix set newpref_
     *     wp db-prefix set newpref_ --skip-success=true
     */
    public function set ( $args, $assoc_args) {
        global $wpdb;
        $new_prefix = isset( $args[0] ) ? $args[0] : $this->_generatePrefix();
        $current_prefix = $wpdb->base_prefix;
        $skipSuccess = isset($assoc_args['skip-success']) ? $assoc_args['skip-success'] : 'false';
        if ( 0 == strcmp( $current_prefix, $new_prefix ) ) {           
            if ('true' !== $skipSuccess) {
                WP_CLI::success( "'{$new_prefix}' is current database prefix value." );
	     }
	     return; 	
        }

        $result = $wpdb->set_prefix( $new_prefix, false );
        if ( is_wp_error( $result ) ) {
            WP_CLI::error( WP_CLI::error_to_string( $result ), self::CODE_WRONG_PREFIX );
        }

        $this->_maintenance_mode( true );
        $plugins = $this->_deactivate_plugins();
        $this->_edit_config( $new_prefix );
        $this->_update_db( $new_prefix, $current_prefix );
        $this->_activate_plugins( $plugins );
        $this->_flush_rewrite_rules();
        $this->_maintenance_mode( false );

	 if ('true' !== $skipSuccess) {
            WP_CLI::success( "Database prefix was successfully changed to '{$new_prefix}'." );
        }
    }

    private function _maintenance_mode( $enable = true ) {
        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
        $err_code = $enable ? self::CODE_FAILED_TURN_ON_MAINTENANCE_MODE : self::CODE_FAILED_TURN_OFF_MAINTENANCE_MODE;

        $upgrader = Utils\get_upgrader( 'WP_Upgrader' );
        if ( !$upgrader->fs_connect( WP_CONTENT_DIR ) ) {
            WP_CLI::error( "Could not connect file system.", $err_code );
        }
        $result = $upgrader->maintenance_mode( $enable );
        if ( is_wp_error( $result ) ) {
            WP_CLI::error( WP_CLI::error_to_string( $result ), $err_code );
        }
    }

    private function _deactivate_plugins () {
        require_once ABSPATH.'wp-admin/includes/plugin.php';
        $active_plugins = array();
        foreach ( get_plugins() as $file => $details ) {
            if (is_plugin_active( $file )) {
                deactivate_plugins( $file );
                if ( is_plugin_active( $file ) ) {
                    WP_CLI::error( "Could not deactivate the '{$details['Name']}' plugin.", self::CODE_FAILED_DEACTIVATE_PLUGIN );
                }
                $active_plugins[] = $file;
            }
        }
        return $active_plugins;
    }

    private function _activate_plugins ( $plugins ) {
        require_once ABSPATH.'wp-admin/includes/plugin.php';
        foreach ( $plugins as $file ) {
            activate_plugins($file);
            if ( !is_plugin_active( $file ) ) {
                $plugin_folder = get_plugins(  '/' . plugin_basename( dirname( $file ) ) );
                $plugin_info = $plugin_folder[basename( $file )];
                WP_CLI::error( "Could not activate the '{$plugin_info['Name']}' plugin.", self::CODE_FAILED_ACTIVATE_PLUGIN );
            }
        }
    }

    private function _edit_config( $new_prefix ) {
        $wp_config_path = Utils\locate_wp_config();

        if ( !is_readable( $wp_config_path ) || false === ( $config_file = file_get_contents( $wp_config_path ) ) ) {
            WP_CLI::error( "Could not open config file.", self::CODE_FAILED_CHANGE_WP_CONFIG );
        }

        $config_file = preg_replace( '/\$table_prefix(.*)$/m', '$table_prefix = \''. $new_prefix .'\';', $config_file );

        if ( !is_writable( $wp_config_path ) || false === file_put_contents( $wp_config_path, $config_file ) ) {
            WP_CLI::error( "Could not modify config file.", self::CODE_FAILED_CHANGE_WP_CONFIG );
        }
    }

    private function _update_db( $new_prefix, $current_prefix ) {
        global $wpdb;

        $tables = $wpdb->get_col( $wpdb->prepare( "SHOW TABLES LIKE %s", like_escape( $current_prefix ) . '%' ) );
        foreach ( $tables as $table_name ) {
            if ( 0 === strpos( $table_name, $current_prefix ) ) {
                $new_table_name = str_replace( $current_prefix, $new_prefix, $table_name );
                if ( false === $wpdb->query( "RENAME TABLE {$table_name} TO {$new_table_name}" ) ) {
                    WP_CLI::error( "Could not change table '{$table_name}' name.", self::CODE_FAILED_CHANGE_DB );
                }
            }
        }

        $result = $wpdb->set_prefix( $new_prefix );
        if ( is_wp_error( $result ) ) {
            WP_CLI::error( WP_CLI::error_to_string( $result ), self::CODE_FAILED_CHANGE_DB );
        }

        if ( false === $wpdb->update(
                $wpdb->options,
                array( 'option_name' => $new_prefix . 'user_roles'),
                array( 'option_name' =>  $current_prefix . 'user_roles' )
            ) ) {
            WP_CLI::error( "Could not update table '{$wpdb->options}'.", self::CODE_FAILED_CHANGE_DB );
        }

        $meta_keys = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, meta_key FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", like_escape( $current_prefix ) . '%' ) );
        foreach ( $meta_keys as $meta_key ) {
            if ( false === $wpdb->update(
                    $wpdb->usermeta,
                    array( 'meta_key' => str_replace( $current_prefix, $new_prefix, $meta_key->meta_key ) ),
                    array( 'meta_key' =>  $meta_key->meta_key, 'user_id' => $meta_key->user_id )
                ) ) {
                WP_CLI::error( "Could not update table '{$wpdb->usermeta}'.", self::CODE_FAILED_CHANGE_DB );
            }
        }
    }

    private function _flush_rewrite_rules() {
        WP_CLI::run_command( array( 'rewrite', 'flush' ), array( 'hard' => true ) );
    }

    /**
    * Generates a prefix of random length.
    *
    * @return string
    */
    private function _generatePrefix()
    {
        $args = array(
            (array)range('a', 'z'),
            (array)range('A', 'Z'),
            (array)range(0, 9),
        );

        srand((float)microtime()*1000000);

        $length = rand(5, 10);

        $prefix = array();
        // add required symbols
        foreach ($args as $arg) {
            if (count($prefix) == $length) {
                shuffle($prefix);
                return implode("", $prefix).'_';
            }
            $prefix[] = $arg[rand(0, count($arg) - 1)];
        }
        // add remaining symbols
        while (count($prefix) < $length) {
            $arg = $args[rand(0, count($args) - 1)];
            $prefix[] = $arg[rand(0, count($arg) - 1)];
        }
        shuffle($prefix);
        return implode("", $prefix).'_';
    }
}

WP_CLI::add_command( 'db-prefix', 'DB_Prefix_Command' );
