<?php

/**
 * Suppress "error - 0 - No summary was found for this file" on phpdoc generation
 *
 * @package WPDataAccess\Utilities
 */

namespace WPDataAccess\Utilities {

	use http\Encoding\Stream;
	use WPDataAccess\Connection\WPDADB;
	use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Exist;
	use WPDataAccess\Data_Dictionary\WPDA_List_Columns_Cache;
	use WPDataAccess\Plugin_Table_Models\WPDA_Publisher_Model;
	use WPDataAccess\Plugin_Table_Models\WPDA_Table_Settings_Model;
	use WPDataAccess\WPDA;

	/**
	 * Class WPDA_Export_Sql
	 *
	 * Exports tables or rows (depending on arguments)
	 *
	 * + Table export
	 *   + One or more tables (batch)
	 *   + Contains a create table statements for every tables exported
	 *   + All records of a tables are exported as one insert statement
	 *   + Contains comments (no reimport without editing possible)
	 * + Row export
	 *   + All records are exported as one insert statement
	 *   + Contains no comments (can be reimported without editing)
	 *
	 * @author  Peter Schulz
	 * @since   1.0.0
	 */
	class WPDA_Export_Sql {

		/**
		 * Database schema name
		 *
		 * @var string
		 */
		protected $schema_name = '';

		/**
		 * Database schema name prefix used in strings
		 *
		 * @var string
		 */
		protected $schema_name_prefix = '';

		/**
		 * Table name(s) of table(s) to be exported
		 *
		 * @var string|array
		 */
		protected $table_list = array();

		/**
		 * Indicates whether to set MySQL environment
		 *
		 * @var string 'on' or 'off'
		 */
		protected $mysql_set;

		/**
		 * Indicates whether comments should be added
		 *
		 * @var string 'on' or 'off'
		 */
		protected $show_comments;

		/**
		 * Indicates whether to add a create table
		 *
		 * @var string 'on' or 'off'
		 */
		protected $show_create;

		/**
		 * Indicates whether table settings should be exported
		 *
		 * @var string 'on' or 'off'
		 */
		protected $include_table_settings;

		/**
		 * Use variable to write output
		 *
		 * @var string
		 */
		protected $output_string;

		/**
		 * Write output to stream if available
		 *
		 * @var null
		 */
		protected $output_stream = null;

		protected $export_with_prefix = false;

		/**
		 * WPDA_Export constructor.
		 *
		 * Make sure the export procedure has sufficient memory.
		 *
		 * @since    2.0.11
		 */
		public function __construct() {
			if ( defined( 'WP_MAX_MEMORY_LIMIT' ) ) {
				$wp_memory_limit      = WP_MAX_MEMORY_LIMIT;
				$current_memory_limit = @ini_get( 'memory_limit' );
				if ( false === $current_memory_limit ||
					 WPDA::convert_memory_to_decimal( $current_memory_limit ) < WPDA::convert_memory_to_decimal( $wp_memory_limit )
				) {
					@ini_set( 'memory_limit', $wp_memory_limit );
				}
			}

			$this->export_with_prefix = 'on' === WPDA::get_option( WPDA::OPTION_BE_EXPORT_VARIABLE_PREFIX );
		}

		/**
		 * Use this method to startan export using method arguments
		 *
		 * @param string       $mysql_set on|off
		 * @param string       $show_comments on|off
		 * @param string       $show_create on|off
		 * @param string       $schema_name Database schema name
		 * @param string|array $table_names Single table name (string) | Table name list (array)
		 * @param string       $export_type table|row
		 * @param string       $include_table_settings on|off
		 *
		 * @since    2.0.7
		 */
		public function export_with_arguments(
			$mysql_set,
			$show_comments,
			$show_create,
			$schema_name,
			$table_names,
			$export_type,
			$include_table_settings = 'on'
		) {
			$this->mysql_set              = 'on' === $mysql_set ? 'on' : 'off';
			$this->show_comments          = 'on' === $show_comments ? 'on' : 'off';
			$this->show_create            = 'on' === $show_create ? 'on' : 'off';
			$this->include_table_settings = 'on' === $include_table_settings ? 'on' : 'off';

			if ( '' !== $schema_name ) {
				$wpdadb = WPDADB::get_db_connection( $schema_name );
				if ( null === $wpdadb ) {
					die( sprintf( __( 'ERROR - Remote database %s not available', 'wp-data-access' ), esc_attr( $this->schema_name ) ) );
				}

				$this->schema_name        = $schema_name;
				$this->schema_name_prefix = "`{$wpdadb->dbname}`.";
			}

			$this->table_list = $table_names;

			if ( 'table' === $export_type ) {
				// Table export.
				$this->export_tables();
			} else {
				// Row export.
				if ( is_array( $this->table_list ) ) {
					// For row exports a single table name must be supplied as a string.
					$this->wrong_arguments();
				} else {
					$this->export_rows();
				}
			}
		}

		/**
		 * Main method to start export
		 *
		 * This method checks arguments and starts the export according to the arguments provided.
		 *
		 * @since   1.0.0
		 */
		public function export() {
			// Check if export is allowed.
			$table_names = isset( $_REQUEST['table_names'] ) ?
				json_encode( WPDA::sanitize_text_field_array( $_REQUEST['table_names'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
			$wp_nonce    = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '?'; // input var okay.
			if (
				! wp_verify_nonce( $wp_nonce, "wpda-export-{$table_names}" ) &&
				! wp_verify_nonce( $wp_nonce, 'wpda-export-' . WPDA::get_current_user_login() )
			) {
				wp_die();
			}

			// Get arguments.
			$this->mysql_set              = isset( $_REQUEST['mysql_set'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['mysql_set'] ) ) : 'on'; // input var okay.
			$this->show_comments          = isset( $_REQUEST['show_comments'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['show_comments'] ) ) : 'on'; // input var okay.
			$this->show_create            = isset( $_REQUEST['show_create'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['show_create'] ) ) : 'on'; // input var okay.
			$this->include_table_settings = isset( $_REQUEST['include_table_settings'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['include_table_settings'] ) ) : 'off'; // input var okay.
			$pub_id                       = isset( $_REQUEST['pub_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['pub_id'] ) ) : ''; // input var okay.

			if ( isset( $_REQUEST['wpdaschema_name'] ) ) {
				$this->schema_name = sanitize_text_field( wp_unslash( $_REQUEST['wpdaschema_name'] ) ); // input var okay.
			} elseif ( '' !== $pub_id ) {
				// Get schema name from publication
				$publication = WPDA_Publisher_Model::get_publication( $pub_id );
				if ( false === $publication ) {
					wp_die();
				}
				$this->schema_name = $publication[0]['pub_schema_name'];
			}

			if ( '' !== $this->schema_name ) {
				$wpdadb = WPDADB::get_db_connection( $this->schema_name );
				if ( null === $wpdadb ) {
					wp_die();
				}
				$this->schema_name_prefix = "`{$wpdadb->dbname}`.";
			}

			if ( isset( $_REQUEST['type'] ) && isset( $_REQUEST['table_names'] ) ) { // input var okay.
				// Get table_name(s) from URL. Type is string for single table export and array for multi table export.
				// Row export implies single table export.
				$this->table_list = WPDA::sanitize_text_field_array( $_REQUEST['table_names'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput

				if ( 'table' === sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) ) { // input var okay.
					// Table export.
					$this->export_tables();
				} else {
					// Row export.
					if ( is_array( $this->table_list ) ) {
						// For row exports a single table name must be supplied as a string.
						$this->wrong_arguments();
					} else {
						$this->export_rows();
					}
				}
			} else {
				$this->wrong_arguments();
			}

			if ( '' !== $pub_id ) {
				wp_die();
			}
		}

		/**
		 * A table export was requested
		 *
		 * @since   1.0.0
		 */
		protected function export_tables() {
			if ( is_array( $this->table_list ) ) {
				// Multiple table export.
				$this->header( 'export' );
				$this->db_begin();

				foreach ( $this->table_list as $table_name ) {
					// Check if table exists to prevent SQL injection.
					$wpda_dictionary_exists = new WPDA_Dictionary_Exist( $this->schema_name, $table_name );
					if ( ! $wpda_dictionary_exists->table_exists() ) {
						wp_die();
					}

					$this->create_table( $table_name );
					$this->insert_rows( $table_name );
				}

				$this->db_end();
			} else {
				// Single table export.
				$table_name = $this->table_list;

				// Check if table exists to prevent SQL injection.
				$wpda_dictionary_exists = new WPDA_Dictionary_Exist( $this->schema_name, $table_name );
				if ( ! $wpda_dictionary_exists->table_exists() ) {
					wp_die();
				}

				$this->header( $table_name );
				$this->db_begin();

				$this->create_table( $table_name );
				$this->insert_rows( $table_name );

				$this->db_end();
			}
		}

		/**
		 * Set export header (filename)
		 *
		 * @param string $file_name Export filename.
		 *
		 * @since   1.0.0
		 */
		protected function header( $file_name ) {
			if ( null === $this->output_stream ) {
				WPDA::sent_header( 'text/plain; charset=utf-8', null, "wpda_{$file_name}.sql" );
			}
		}

		/**
		 * Set MySQL environment
		 *
		 * @since   1.0.0
		 */
		protected function db_begin() {
			if ( 'off' === $this->mysql_set ) {
				return;
			}

			$wpdadb = WPDADB::get_db_connection( $this->schema_name );
			if ( null === $wpdadb ) {
				wp_die();
			}

			$this->output_string = '';

			$this->output_string .= "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n";
			$this->output_string .= "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n";
			$this->output_string .= "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n";
			$this->output_string .= '/*!40101 SET NAMES ' . esc_attr( $wpdadb->charset ) . " */;\n\n";

			$this->write_output();
		}

		/**
		 * Write create table statement
		 *
		 * @param string $table_name Database table name.
		 *
		 * @return boolean Indicates whether table exists for further processing.
		 * @since   1.0.0
		 */
		protected function create_table( $table_name ) {
			global $wpdb;
			$wpdadb = WPDADB::get_db_connection( $this->schema_name );
			if ( null === $wpdadb ) {
				wp_die();
			}

			$save_suppress_errors    = $wpdadb->suppress_errors;
			$wpdadb->suppress_errors = true;

			if ( 'off' !== $this->show_create ) {
				$engine = WPDA::get_table_engine( $this->schema_name, $table_name );
				if ( 'connect' === strtolower( $engine ) ) {
					// Remove table options for CONNECT tables
                    // NO_TABLE_OPTIONS is deprecated in V8
					// $wpdadb->query( "SET sql_mode = 'NO_TABLE_OPTIONS'" );
				}
				$query = "show create table {$this->schema_name_prefix}`" . str_replace( '`', '', (string) $table_name ) . '`';
				$ctcmd = $wpdadb->get_results( $query, 'ARRAY_A' ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
			}

			$this->output_string = '';
			if ( $wpdadb->num_rows > 0 ) {
				if ( 'off' !== $this->show_comments ) {
					$this->output_string .= "--\n";
					if (
						$this->export_with_prefix &&
						$wpdb->dbname === $this->schema_name &&
						( WPDA::is_wp_table( $table_name ) || WPDA::is_wpda_table( $table_name ) )
					) {
						$this->output_string .= '-- Create table `{wp_prefix}' . esc_attr( substr( $table_name, strlen( $wpdb->prefix ) ) ) . "`\n";
					} else {
						$this->output_string .= '-- Create table `' . esc_attr( $table_name ) . "`\n";
					}
					$this->output_string .= "--\n";
				}

				if ( 'off' !== $this->show_create ) {
					$create_table_statement = $ctcmd[0]['Create Table'];
					if (
						$this->export_with_prefix &&
						$wpdb->dbname === $this->schema_name &&
						( WPDA::is_wp_table( $table_name ) || WPDA::is_wpda_table( $table_name ) )
					) {
						$table_name_position = stripos( $create_table_statement, $table_name );
						if ( $table_name_position > 0 ) {
							$create_table_statement_saved = $create_table_statement;
							$create_table_statement       = substr( $create_table_statement, 0, $table_name_position );
							$create_table_statement      .= '{wp_prefix}' . esc_attr( substr( $table_name, strlen( $wpdb->prefix ) ) );
							$create_table_statement      .= substr( $create_table_statement_saved, $table_name_position + strlen( $table_name ) );
						}
					}
					$this->output_string .= wp_kses_data( $create_table_statement ) . ";\n\n";
				}

				$table_exists = true;
			} else {
				if ( 'off' !== $this->show_comments ) {
					$this->output_string .= "--\n";
					$this->output_string .= '-- Table `' . esc_attr( $table_name ) . "` found\n";
					$this->output_string .= "--\n\n";
				}

				$table_exists = false;
			}

			$this->write_output();

			$wpdadb->suppress_errors = $save_suppress_errors;

			return $table_exists;
		}

		/**
		 * Write insert into statement
		 *
		 * @param string  $table_name Database table name.
		 * @param string  $where Where clause.
		 * @param boolean $show_comments Argument is needed for direct call from WPDA_Repository! (do not remove).
		 *
		 * @since   1.0.0
		 */
		public function insert_rows( $table_name, $where = '', $show_comments = true ) {
			$wpdadb = WPDADB::get_db_connection( $this->schema_name );
			if ( null === $wpdadb ) {
				wp_die();
			}

			$save_suppress_errors    = $wpdadb->suppress_errors;
			$wpdadb->suppress_errors = true;

			$settings_db = WPDA_Table_Settings_Model::query( $table_name, $this->schema_name );
			if ( isset( $settings_db[0]['wpda_table_settings'] ) ) {
				$settings_db_custom = json_decode( $settings_db[0]['wpda_table_settings'] );
				if ( isset( $settings_db_custom->table_settings->query_buffer_size ) ) {
					$query_buffer_size = $settings_db_custom->table_settings->query_buffer_size;
				} else {
					$query_buffer_size = 0;
				}
			} else {
				$query_buffer_size = 0;
			}

			$this->output_string = '';

			$query = "select * from {$this->schema_name_prefix}`" . str_replace( '`', '', (string) $table_name ) . "` $where";
			if ( is_numeric( $query_buffer_size ) && $query_buffer_size > 0 ) {
				set_time_limit(0);
				$i    = 0;
				$sql  = $query . ' limit ' . $query_buffer_size;
				$rows = $wpdadb->get_results( $sql, 'ARRAY_A' ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
				while ( $wpdadb->num_rows > 0 ) {
					$this->insert_rows_buffer( $rows, $table_name, $where, $show_comments );

					$i++;
					$sql  = $query . ' limit ' . $query_buffer_size . ' offset ' . ( $i * $query_buffer_size );
					$rows = $wpdadb->get_results( $sql, 'ARRAY_A' ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
				}

				if ( 1 === $i && 0 == $wpdadb->num_rows ) {
					$this->empty_table( $table_name, $show_comments );
				}
			} else {
				$rows = $wpdadb->get_results( $query, 'ARRAY_A' ); // phpcs:ignore Standard.Category.SniffName.ErrorCode

				if ( $wpdadb->num_rows > 0 ) {
					$this->insert_rows_buffer( $rows, $table_name, $where, $show_comments );
				} else {
					$this->empty_table( $table_name, $show_comments );
				}
			}

			if ( 'on' === $this->include_table_settings ) {
				// Export table settings
				$this->export_table_settings( $table_name, $show_comments );
			}

			$this->write_output();

			$wpdadb->suppress_errors = $save_suppress_errors;
		}

		private function empty_table( $table_name, $show_comments ) {
			// Empty table, nothing to export.
			if ( $show_comments && 'off' !== $this->show_comments ) {
				$this->output_string .= "--\n";
				$this->output_string .= '-- No rows to export from empty table `' . esc_attr( $table_name ) . "`\n";
				$this->output_string .= "--\n\n";
			}
		}

		/**
		 * Write insert into statements for buffered rows
		 *
		 * @param array   $rows Buffered rows
		 * @param string  $table_name Database table name.
		 * @param string  $where Where clause.
		 * @param boolean $show_comments Argument is needed for direct call from WPDA_Repository! (do not remove).
		 *
		 * @since   1.0.0
		 */
		private function insert_rows_buffer( $rows, $table_name, $where, $show_comments ) {
			global $wpdb;

			$this->output_string = '';

			// Prepare row export: get column names and data types.
			$wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $this->schema_name, $table_name );
			$table_columns     = $wpda_list_columns->get_table_columns();

			// Create array for fast column_name based access.
			$column_data_types = $this->column_data_types( $table_columns );

			// Exports rows.
			if ( $show_comments && 'off' !== $this->show_comments ) {
				$this->output_string .= "--\n";
				if (
					$this->export_with_prefix &&
					$wpdb->dbname === $this->schema_name &&
					( WPDA::is_wp_table( $table_name ) || WPDA::is_wpda_table( $table_name ) )
				) {
					$this->output_string .= '-- Export table `{wp_prefix}' . esc_attr( substr( $table_name, strlen( $wpdb->prefix ) ) ) . "`\n";
				} else {
					$this->output_string .= '-- Export table `' . esc_attr( $table_name ) . "`\n";
				}
				$this->output_string .= "--\n";
			}

			if ( ( $show_comments && '' !== $where ) && 'off' !== $this->show_comments ) {
				$this->output_string .= '-- Condition: `' . esc_attr( $where ) . "`\n";
				$this->output_string .= "--\n";
			}

			$this->write_output();

			if (
				$this->export_with_prefix &&
				( $wpdb->dbname === $this->schema_name || '' === $this->schema_name ) &&
				( WPDA::is_wp_table( $table_name ) || WPDA::is_wpda_table( $table_name ) )
			) {
				$insert_statement = 'INSERT INTO `{wp_prefix}' . esc_attr( substr( str_replace( '`', '', (string) $table_name ), strlen( $wpdb->prefix ) ) ) . '` ';
			} else {
				$insert_statement = 'INSERT INTO `' . esc_attr( str_replace( '`', '', (string) $table_name ) ) . '` ';
			}

			$process_first_row = true;
			foreach ( $rows[0] as $column_name => $column_value ) {
				if (
					! (
						WPDA::is_wpda_table( $table_name ) &&
						(
							'pub_id' === $column_name ||
							'csv_id' === $column_name ||
							'menu_id' === $column_name ||
							'report_id' === $column_name
						)
					)
				) {
					$insert_statement .= $process_first_row ? '(' : ', ';
					$insert_statement .= '`' . esc_attr( str_replace( '`', '', (string) $column_name ) ) . '`';

					$process_first_row = false;
				}
			}

			$insert_statement .= ') VALUES ';

			foreach ( $rows as $row ) {
				$this->output_string .= $insert_statement . '(';

				$keys        = array_keys( $row );//phpcs:ignore - 8.1 proof
				$last_column = end( $keys );//phpcs:ignore - 8.1 proof
				foreach ( $row as $column_name => $column_value ) {
					if (
						! (
							WPDA::is_wpda_table( $table_name ) &&
							(
								'pub_id' === $column_name ||
								'csv_id' === $column_name ||
								'menu_id' === $column_name ||
								'report_id' === $column_name
							)
						)
					) {
						if ( $this->is_numeric( $column_data_types[ $column_name ] ) ) {
							if ( is_null( $column_value ) ) {
								$this->output_string .= 'null';
							} else {
								$this->output_string .= esc_attr( $column_value );
							}
						} else {
							if (
								WPDA::column_is_schema_name( $table_name, $column_name ) &&
								( '' === $column_value || $wpdb->dbname === $column_value )
							) {
								$this->output_string .= "'{wp_schema}'";
							} else {
								if ( is_null( $column_value ) ) {
									$this->output_string .= 'null';
								} else {
									$this->output_string .= "'" . esc_sql( $column_value ) . "'";
								}
							}
						}
						if ( $column_name !== $last_column ) {
							$this->output_string .= ',';
						}
					}
				}

				$this->output_string .= ");\n";

				$this->write_output();
			}

			if ( 'off' !== $this->show_comments ) {
				$this->output_string .= "\n";
			}
		}

		/**
		 * Export table settings
		 *
		 * @param         $table_name Table for which settings will be exported
		 * @param boolean                                              $show_comments Argument is needed for direct call from WPDA_Repository! (do not remove).
		 *
		 * @since   2.6.1
		 */
		protected function export_table_settings( $table_name, $show_comments = true ) {
			global $wpdb;

			if ( $show_comments && 'off' !== $this->show_comments ) {
				$this->output_string .= "--\n";
				$this->output_string .= '-- Export table settings for table `' . esc_attr( $table_name ) . "`\n";
				$this->output_string .= "--\n";
			}

			if ( '' === $this->schema_name || $wpdb->dbname === $this->schema_name ) {
				$schema_name = '{wp_schema}';
			} else {
				$schema_name = $this->schema_name;
			}

			if ( 'on' === $this->include_table_settings ) {
				// Export column labels
				$rows = $wpdb->get_results(
					$wpdb->prepare(
						"select * from {$wpdb->prefix}wpda_table_settings where wpda_table_name = %s",
						array(
							$table_name,
						)
					),
					'ARRAY_A'
				); // phpcs:ignore Standard.Category.SniffName.ErrorCode
				if ( 1 === $wpdb->num_rows ) {
					$this->output_string .=
						'DELETE FROM `{wp_prefix}wpda_table_settings` ' .
						"WHERE `wpda_table_name` = '" . esc_attr( $table_name ) . "';";
					$this->output_string .= "\n";
					$this->output_string .=
						'INSERT INTO `{wp_prefix}wpda_table_settings` ' .
						'(`wpda_schema_name`, `wpda_table_name`, `wpda_table_settings`) ' .
						'VALUES ' .
						"('" . esc_sql( $schema_name ) . "', '" . esc_sql( $table_name ) . "', '" .
						esc_sql( $rows[0]['wpda_table_settings'] ) . "');";
					$this->output_string .= "\n";
				}

				// Export media columns
				$rows                 = $wpdb->get_results(
					$wpdb->prepare(
						"select * from {$wpdb->prefix}wpda_media where media_table_name = %s",
						array(
							$table_name,
						)
					),
					'ARRAY_A'
				); // phpcs:ignore Standard.Category.SniffName.ErrorCode
				$this->output_string .=
					'DELETE FROM `{wp_prefix}wpda_media` ' .
					"WHERE `media_table_name` = '" . esc_attr( $table_name ) . "';";
				$this->output_string .= "\n";
				foreach ( $rows as $row ) {
					$this->output_string .=
						'INSERT INTO `{wp_prefix}wpda_media` ' .
						'(`media_schema_name`, `media_table_name`, `media_column_name`, `media_type`, `media_activated`) ' .
						'VALUES ' .
						"('" . esc_sql( $schema_name ) . "', '" . esc_sql( $table_name ) . "', '" .
						esc_sql( $row['media_column_name'] ) . "', '" . esc_sql( $row['media_type'] ) . "', '" .
						esc_sql( $row['media_activated'] ) . "');";
					$this->output_string .= "\n";
				}

				// Export table menus
				$rows                 = $wpdb->get_results(
					$wpdb->prepare(
						"select * from {$wpdb->prefix}wpda_menus where menu_table_name = %s",
						array(
							$table_name,
						)
					),
					'ARRAY_A'
				); // phpcs:ignore Standard.Category.SniffName.ErrorCode
				$this->output_string .=
					'DELETE FROM `{wp_prefix}wpda_menus` ' .
					"WHERE `menu_table_name` = '" . esc_attr( $table_name ) . "';";
				$this->output_string .= "\n";
				foreach ( $rows as $row ) {
					$this->output_string .=
						'INSERT INTO `{wp_prefix}wpda_menus` ' .
						'(`menu_schema_name`, `menu_table_name`, `menu_name`, `menu_slug`, `menu_role`) ' .
						'VALUES ' .
						"('" . esc_sql( $schema_name ) . "', '" . esc_sql( $table_name ) . "', '" .
						esc_sql( $row['menu_name'] ) . "', '" . esc_sql( $row['menu_slug'] ) . "', '" .
						esc_sql( $row['menu_role'] ) . "');";
					$this->output_string .= "\n";
				}
			}

			$this->output_string .= "\n";
		}

		/**
		 * Save column data types
		 *
		 * This method creates a named array for all column names of a table in form:
		 * 'column_name' => 'data_type'
		 *
		 * Argument $table_columns can be retrieved from WPDA_List_Columns->set_table_columns(). It must be prepared
		 * however with the idea that the instance of WPDA_List_Columns can be reused for best performance.
		 *
		 * In fact this is just an array conversion.
		 *
		 * @param array $table_columns Column_names and data_types of a table (table name not used here).
		 *
		 * @return array Named array 'column_name' => 'data_type' for all columns in the table.
		 * @since   1.0.0
		 */
		protected function column_data_types( $table_columns ) {
			$column_data_types = array();

			foreach ( $table_columns as $column_value ) {
				$column_data_types[ $column_value['column_name'] ] = $column_value['data_type'];
			}

			return $column_data_types;
		}

		/**
		 * Check if data type is numeric
		 *
		 * @param string $data_type Data type (simple).
		 *
		 * @return bool
		 * @since   1.0.0
		 */
		protected function is_numeric( $data_type ) {
			return ( 'number' === WPDA::get_type( $data_type ) );
		}

		/**
		 * Set back MySQL environment
		 *
		 * @since   1.0.0
		 */
		protected function db_end() {
			if ( 'off' === $this->mysql_set ) {
				return;
			}

			$this->output_string = '';

			$this->output_string .= "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n";
			$this->output_string .= "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n";
			$this->output_string .= "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n";

			$this->write_output();
		}

		/**
		 * Processing on invalid arguments
		 *
		 * @since   1.0.0
		 */
		protected function wrong_arguments() {
			wp_die();
		}

		/**
		 * A row export was requested
		 *
		 * @since   1.0.0
		 */
		protected function export_rows() {
			// Check if table exists to prevent SQL injection.
			$wpda_dictionary_exists = new WPDA_Dictionary_Exist( $this->schema_name, $this->table_list );
			if ( ! $wpda_dictionary_exists->table_exists() ) {
				wp_die();
			}

			// Get table columns and data types.
			$wpda_list_columns = WPDA_List_Columns_Cache::get_list_columns( $this->schema_name, $this->table_list );
			$table_columns     = $wpda_list_columns->get_table_columns();

			// Create array for fast column_name based access.
			$column_data_types = $this->column_data_types( $table_columns );

			// Get primary key columns.
			$table_primary_key = $wpda_list_columns->get_table_primary_key();

			// Check validity request. All primary key columns must be supplied. Return error if
			// primary key columns are missing.
			foreach ( $table_primary_key as $key ) {
				if ( ! isset( $key ) ) {
					$this->wrong_arguments();
				}
			}

			global $wpdb;

			// Build where clause based on primary key.
			$where = '';

			// Use first column of the primary key to loop through arguments. Add additional arguments in the loop.
			// A mismatch in the number of argument is possible as long as the columns match based on the first column
			// of the primary key. Other mismatches won't be taken into account.
			$count_pk = count( ( array ) $_REQUEST[ $table_primary_key[0] ] );//phpcs:ignore - 8.1 proof
			for ( $i = 0; $i < $count_pk; $i ++ ) {
				$and = '';
				foreach ( $table_primary_key as $key ) {
					$and .= '' === $and ? '(' : ' and ';
					if ( $this->is_numeric( $column_data_types[ $key ] ) ) {
						$and .= $wpdb->prepare(
							'`%1s` = %d', // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
							array(
								WPDA::remove_backticks( $key ),
								sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $i ] ) ),
							)
						); // phpcs:ignore Standard.Category.SniffName.ErrorCode
					} else {
						$and .= $wpdb->prepare(
							'`%1s` = %s', // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders
							array(
								WPDA::remove_backticks( $key ),
								sanitize_text_field( wp_unslash( $_REQUEST[ $key ][ $i ] ) ),
							)
						); // phpcs:ignore Standard.Category.SniffName.ErrorCode
					}
				}

				$and .= '' === $and ? '' : ')';

				$where .= '' === $where ? ' where ' : ' or ';
				$where .= $and;
			}

			$this->header( $this->table_list );
			$this->db_begin();
			$this->insert_rows( $this->table_list, $where );
			$this->db_end();
		}

		/**
		 * Define output stream
		 *
		 * Used to stream an export to a file. Streaming helps to support exports of large files.
		 *
		 * @param Stream $output_stream Handle to output stream
		 *
		 * @since 2.0.13
		 */
		public function set_output_stream( $output_stream ) {
			$this->output_stream = $output_stream;
		}

		/**
		 * Depending on the type of export (streaming or echoing) the output is written to the export file.
		 *
		 * @since 2.0.13
		 */
		protected function write_output() {
			if ( null === $this->output_stream ) {
				echo $this->output_string; // phpcs:ignore WordPress.Security.EscapeOutput
			} else {
				fwrite( $this->output_stream, $this->output_string );
			}
			$this->output_string = '';
		}

	}

}
