<?php

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

namespace WPDataAccess {

	use WP_Data_Access_Admin;
	use WPDataAccess\Connection\WPDADB;
	use WPDataAccess\Data_Dictionary\WPDA_Dictionary_Lists;
	use WPDataAccess\Data_Dictionary\WPDA_List_Columns_Cache;
	use WPDataAccess\Utilities\WPDA_Message_Box;
	use WPDataProjects\WPDP;

	/**
	 * Class WPDA
	 *
	 * Plugin default values and settings are managed through this class. Every plugin option has a default value
	 * which is maintained in an array together with the option name. Options are only saved in $wpdb->options when
	 * they are changed. Otherwise the default values are used. After reading option values from $wpdb->options the
	 * values are cached as many of them are used in multiple
	 * values are cached as many of them are used in multiple
	 * places during the processing of a request.
	 *
	 * @author  Peter Schulz
	 * @since   1.0.0
	 */
	class WPDA {

		// SAVING SPACE - According to the plugin guidelines it is allowed to include external fonts:
		// https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/#8-plugins-may-not-send-executable-code-via-third-party-systems
		const CDN_FONTAWESOME = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/';
		const GOOGLE_CHARTS   = 'https://www.gstatic.com/charts/loader.js';

		/**
		 * Label for WordPress tables
		 */
		const TABLE_TYPE_WP = 'wordpress table';
		/**
		 * Label for WPDA plugin tables
		 */
		const TABLE_TYPE_WPDA = 'plugin table';

		// Options are stored in arrays (OPTION_ARRAYS):
		// [0] = option name (as saved in wp_options).
		// [1] = default value (used if option is not available in wp_options).
		// Application options.
		/**
		 * Option wpda_version and it's default value
		 */
		const OPTION_WPDA_VERSION         = array( 'wpda_version', '5.5.63' );
		const OPTION_WPDA_CLIENT_VERSION  = array( 'wpda_client_version', '1.0.63' );
		const OPTION_WPDA_UPGRADED        = array( 'wpda_upgraded', false );
		/**
		 * Option wpda_setup_error and it's default value
		 */
		const OPTION_WPDA_SETUP_ERROR = array(
			'wpda_setup_error',
			''
		); // Values: off = do not display setup errors, other = show.
		/**
		 * Option wpda_uninstall_tables and it's default value
		 */
		const OPTION_WPDA_UNINSTALL_TABLES = array( 'wpda_uninstall_tables', 'off' ); // On uninstall drop WPDA tables.
		/**
		 * Option wpda_uninstall_options and it's default value
		 */
		const OPTION_WPDA_UNINSTALL_OPTIONS = array( 'wpda_uninstall_options', 'off' ); // On uninstall delete WPDA options.
		/**
		 * Option wpda_datatables_version and it's default value
		 */
		const OPTION_WPDA_DATATABLES_VERSION = array( 'wpda_datatables_version', '1.11.3');
		/**
		 * Option wpda_datatables_responsive_version and it's default value
		 */
		const OPTION_WPDA_DATATABLES_RESPONSIVE_VERSION = array( 'wpda_datatables_responsive_version', '2.4.0');

		const OPTION_PLUGIN_HIDE_NOTICES = array( 'wpda_plugin_hide_notices', 'on');

		const OPTION_PLUGIN_HIDE_ADMIN_MENU = array( 'wpda_plugin_hide_admin_menu', 'off');

		const OPTION_PLUGIN_PANEL_COOKIES = array( 'wpda_plugin_panel_cookies', 'clear');

		const OPTION_PLUGIN_SECRET_KEY_DEFAULT = 'enter-your-secret-key-here';
		const OPTION_PLUGIN_SECRET_IV_DEFAULT  = 'enter-your-secret-iv-here';

        const OPTION_PLUGIN_LEGACY_TOOLS =
            array(
                'wpda_plugin_legacy_tools',
                array(
                    'tables'     => array(false, 0),
                    'forms'      => array(false, 0),
                    'templates'  => array(false, 0),
                    'designer'   => array(false, 0),
                    'dashboards' => array(false, 0),
                    'charts'     => array(false, 0),
                )
        );

		const OPTION_PLUGIN_SECRET_KEY = array( 'wpda_plugin_secret_key', self::OPTION_PLUGIN_SECRET_KEY_DEFAULT);
		const OPTION_PLUGIN_SECRET_IV  = array( 'wpda_plugin_secret_iv', self::OPTION_PLUGIN_SECRET_IV_DEFAULT);

		const OPTION_PLUGIN_SONCE_SEED = array( 'wpda_plugin_sonce_seed', 'ALSKDFHIUWEALDSKNCNKSDJAKDJHSFAKSDFGFKOJHORITIHGMRTGHMHL');

		const OPTION_PLUGIN_WPDATAACCESS_POST = array( 'wpda_plugin_wpdataaccess_post', 'on');
		const OPTION_PLUGIN_WPDATAACCESS_PAGE = array( 'wpda_plugin_wpdataaccess_page', 'on');
		const OPTION_PLUGIN_WPDADIEHARD_POST  = array( 'wpda_plugin_wpdadiehard_post', 'on');
		const OPTION_PLUGIN_WPDADIEHARD_PAGE  = array( 'wpda_plugin_wpdadiehard_page', 'on');
		const OPTION_PLUGIN_WPDADATAFORMS_POST = array( 'wpda_plugin_wpdadataforms_post', 'on');
		const OPTION_PLUGIN_WPDADATAFORMS_PAGE = array( 'wpda_plugin_wpdadataforms_page', 'on');
		const OPTION_PLUGIN_WPDADATAFORMS_ALLOW_ANONYMOUS_ACCESS = array( 'wpda_plugin_wpdadataforms_allow_anonymous_access', 'off');
		const OPTION_PLUGIN_WPDAREPORT_POST = array( 'wpda_plugin_wpdareport_post', 'on');
		const OPTION_PLUGIN_WPDAREPORT_PAGE = array( 'wpda_plugin_wpdareport_page', 'on');

		// MySQL date and time formats
		const DB_DATE_FORMAT     = 'Y-m-d';
		const DB_TIME_FORMAT     = 'H:i:s';
		const DB_DATETIME_FORMAT = 'Y-m-d H:i:s';

		// Plugin options. (date and time settings)
		const OPTION_PLUGIN_DATE_FORMAT      = array( 'wpda_plugin_date_format', 'Y-m-d');
		const OPTION_PLUGIN_DATE_PLACEHOLDER = array( 'wpda_plugin_date_placeholder', 'yyyy-mm-dd');
		const OPTION_PLUGIN_TIME_FORMAT      = array( 'wpda_plugin_time_format', 'H:i');
		const OPTION_PLUGIN_TIME_PLACEHOLDER = array( 'wpda_plugin_time_placeholder', 'hh:mi');
		const OPTION_PLUGIN_SET_FORMAT       = array( 'wpda_plugin_set_format', 'csv');

		// Plugin debug mode
		const OPTION_PLUGIN_DEBUG = array( 'wpda_plugin_debug', 'off');

		// Back-end options.
		/**
		 * Option wpda_be_load_datatables and it's default value
		 */
		const OPTION_BE_LOAD_DATATABLES = array( 'wpda_be_load_datatables', 'on');
		/**
		 * Option wpda_be_load_datatables_response and it's default value
		 */
		const OPTION_BE_LOAD_DATATABLES_RESPONSE = array( 'wpda_be_load_datatables_response', 'on');
		/**
		 * Option wpda_be_table_access and it's default value
		 */
		const OPTION_BE_TABLE_ACCESS = array( 'wpda_be_table_access', 'show'); // Values: show, hide and select.
		/**
		 * Option wpda_be_table_access_selected and it's default value
		 */
		const OPTION_BE_TABLE_ACCESS_SELECTED = array( 'wpda_be_table_access_selected', ''); // Tables authorized in WPDA.
		/**
		 * Option wpda_be_view_link and it's default value
		 */
		const OPTION_BE_VIEW_LINK = array( 'wpda_be_view_link', 'on'); // Show view link in list table.
		/**
		 * Option wpda_be_allow_insert and it's default value
		 */
		const OPTION_BE_ALLOW_INSERT = array(
			'wpda_be_allow_insert',
			'on'
		); // Show insert link in list table (simple form).
		/**
		 * Option wpda_be_allow_update and it's default value
		 */
		const OPTION_BE_ALLOW_UPDATE = array(
			'wpda_be_allow_update',
			'on'
		); // Show update link in list table (simple form).
		/**
		 * Option wpda_be_allow_delete and it's default value
		 */
		const OPTION_BE_ALLOW_DELETE = array( 'wpda_be_allow_delete', 'on'); // Show delete link in list table.
		/**
		 * Option wpda_be_wpda_export_rows and it's default value
		 */
		const OPTION_BE_EXPORT_ROWS = array(
			'wpda_be_wpda_export_rows',
			'on'
		); // Show export link in list table (row export).
		/**
		 * Option wpda_be_wpda_export_variable_prefix and it's default value
		 */
		const OPTION_BE_EXPORT_VARIABLE_PREFIX = array(
			'wpda_be_wpda_export_variable_prefix',
			'on'
		); // Allows to import into repository with different wpdb prefix.
		/**
		 * Option wpda_be_wpda_allow_imports and it's default value
		 */
		const OPTION_BE_ALLOW_IMPORTS = array( 'wpda_be_wpda_allow_imports', 'on'); // Allow to import data (main only).
		/**
		 * Option wpda_be_confirm_export and it's default value
		 */
		const OPTION_BE_CONFIRM_EXPORT = array( 'wpda_be_confirm_export', 'off'); // Ask for confirmation before exporting.
		/**
		 * Option wpda_be_confirm_view and it's default value
		 */
		const OPTION_BE_CONFIRM_VIEW = array( 'wpda_be_confirm_view', 'off'); // Ask for confirmation before viewing.
		/**
		 * Option wpda_be_pagination and it's default value
		 */
		const OPTION_BE_PAGINATION = array( 'wpda_be_pagination', '10');
		/**
		 * Option wpda_be_remember_search and it's default value
		 */
		const OPTION_BE_REMEMBER_SEARCH = array( 'wpda_be_remember_search', 'on');
		/**
		 * Option wpda_be_innodb_count and it's default value
		 */
		const OPTION_BE_INNODB_COUNT = array( 'wpda_be_innodb_count', 100000);
		/**
		 * Option wpda_be_design_mode and it's default value
		 */
		const OPTION_BE_DESIGN_MODE = array( 'wpda_be_design_mode', 'advanced'); // Default design mode (basic/advanced).
		/**
		 * Option wpda_be_text_wrap_switch and it's default value
		 */
		const OPTION_BE_TEXT_WRAP_SWITCH = array( 'wpda_be_text_wrap_switch', 'off');
		/**
		 * Option wpda_be_text_wrap and it's default value
		 */
		const OPTION_BE_TEXT_WRAP = array( 'wpda_be_text_wrap', 400);
		/**
		 * Option wpda_be_hide_button_icons and it's default value
		 */
		const OPTION_BE_HIDE_BUTTON_ICONS = array( 'wpda_be_hide_button_icons', 'off');

		// Front-end options.
		/**
		 * Option wpda_fe_load_datatables and it's default value
		 */
		const OPTION_FE_LOAD_DATATABLES = array( 'wpda_fe_load_datatables', 'on');
		/**
		 * Option wpda_fe_load_datatables_response and it's default value
		 */
		const OPTION_FE_LOAD_DATATABLES_RESPONSE = array( 'wpda_fe_load_datatables_response', 'on');
		/**
		 * Option wpda_fe_table_access and it's default value
		 */
		const OPTION_FE_TABLE_ACCESS = array( 'wpda_fe_table_access', 'select'); // Values: hide, show, select.
		/**
		 * Option wpda_fe_table_access_selected and it's default value
		 */
		const OPTION_FE_TABLE_ACCESS_SELECTED = array( 'wpda_fe_table_access_selected', ''); // Tables authorized in WPDA.
		/**
		 * Option wpda_fe_pagination and it's default value
		 */
		const OPTION_FE_PAGINATION = array( 'wpda_fe_pagination', '10');
		const OPTION_FE_ADD_PROJECTS_TO_TOOLBAR = array( 'wpda_fe_add_projects_to_toolbar', 'on');

		// Manage Repository options.
		/**
		 * Option wpda_mr_keep_backup_tables and it's default value
		 */
		const OPTION_MR_KEEP_BACKUP_TABLES = array( 'wpda_mr_keep_backup_tables', 'on');
		const OPTION_MR_BACKUP_TABLES_KEPT_DEFAULT = '3';
		const OPTION_MR_BACKUP_TABLES_KEPT = array( 'wpda_mr_backup_tables_kept', self::OPTION_MR_BACKUP_TABLES_KEPT_DEFAULT);

		// Data Backup options.
		/**
		 * Option wpda_db_local_path and it's default value
		 */
		const OPTION_DB_LOCAL_PATH = array( 'wpda_db_local_path', '');
		/**
		 * Option wpda_db_dropbox_path and it's default value
		 */
		const OPTION_DB_DROPBOX_PATH = array( 'wpda_db_dropbox_path', '');

		// Data Tables options.
		/**
		 * Option wpda_dp_publication_roles and it's default value
		 */
		const OPTION_DP_PUBLICATION_ROLES = array( 'wpda_dp_publication_roles', '');
		const OPTION_DP_LANGUAGE          = array( 'wpda_dp_language', 'English');
		const OPTION_DP_JSON_EDITING      = array( 'wpda_dp_json_editing', 'validate');
		const OPTION_DP_STYLE             = array( 'wpda_dp_style', 'default');

		// Role Management
		const OPTION_WPDA_ENABLE_ROLE_MANAGEMENT = array( 'wpda_rm_enable_role_management', 'off');
		const OPTION_WPDA_USE_ROLES_IN_SHORTCODE = array( 'wpda_rm_use_roles_in_shortcode', 'off');

		// Prefixes to store backend access for non WP databases
		const BACKEND_OPTIONNAME_DATABASE_ACCESS   = 'WPDA_BE_TABLE_ACCESS_';
		const BACKEND_OPTIONNAME_DATABASE_SELECTED = 'WPDA_BE_TABLE_SELECTED_';

		// Prefixes to store frontend access for non WP databases
		const FRONTEND_OPTIONNAME_DATABASE_ACCESS   = 'WPDA_FE_TABLE_ACCESS_';
		const FRONTEND_OPTIONNAME_DATABASE_SELECTED = 'WPDA_FE_TABLE_SELECTED_';

		const WPDA_DT_UI_THEME_DEFAULT   = array( 'wpda_dt_ui_theme_default', 'smoothness');

		/**
		 * List of plugin tables
		 */
		const WPDA_TABLES = array(
			'wpda_app'            => true,
			'wpda_app_container'  => true,
			'wpda_app_apps'       => true,
			'wpda_logging'        => true,
			'wpda_menus'          => true,
			'wpda_table_settings' => true,
			'wpda_table_design'   => true,
			'wpda_publisher'      => true,
			'wpda_media'          => true,
			'wpda_project'        => true,
			'wpda_project_page'   => true,
			'wpda_project_table'  => true,
			'wpda_csv_uploads'    => true,
		);

		/**
		 * List containing all plugin tables
		 *
		 * @var array
		 */
		static protected $wpda_tables = array();

		// Option values once queried from wp_options are stored in an array for re-use during request to prevent
		// executing same query on wp_options multiple times.
		/**
		 * Options cache array
		 *
		 * @var array
		 */
		static protected $option_cache = array();

		/**
		 * List containing all WordPress tables
		 *
		 * @var array
		 */
		static protected $wp_tables = array();

		static protected $plugin_pages = array(
			WP_Data_Access_Admin::PAGE_MAIN,
			WP_Data_Access_Admin::PAGE_NAVI,
			WP_Data_Access_Admin::PAGE_APPS,
			WP_Data_Access_Admin::PAGE_DASHBOARD,
			WP_Data_Access_Admin::PAGE_SETTINGS,
			WP_Data_Access_Admin::PAGE_EXPLORER,
			WP_Data_Access_Admin::PAGE_QUERY_BUILDER,
			WP_Data_Access_Admin::PAGE_PUBLISHER,
			WP_Data_Access_Admin::PAGE_DESIGNER,
			WP_Data_Access_Admin::PAGE_MY_TABLES,
			WP_Data_Access_Admin::PAGE_CHARTS,
			WPDP::PAGE_MAIN,
			WPDP::PAGE_TEMPLATES,
		);

		public static function is_plugin_page( $page ) {
			return (
				is_scalar( $page ) &&
				(
					'wpda_wpdp_' === substr( $page, 0, 10 ) ||
					WP_Data_Access_Admin::PAGE_EXPLORER === substr( $page, 0, 13 ) ||
					in_array( $page, self::$plugin_pages) //phpcs:ignore - 8.1 proof
				)
			);
		}

		/**
		 * Translated table label
		 *
		 * Returns a translated table label. The label depends on the type of table. Provided through a function to
		 * support internationalization.
		 *
		 * @param string $table_type Table type (use WPDA constants).
		 *
		 * @return string Translated table type.
		 * @since   1.0.0
		 *
		 */
		public static function get_table_type_text( $table_type ) {

			switch ( $table_type ) {

				case self::TABLE_TYPE_WP:
					return __( 'WordPress table', 'wp-data-access' );

				case self::TABLE_TYPE_WPDA:
					return __( 'plugin table', 'wp-data-access' );

				default:
					return $table_type;

			}

		}

		/**
		 * Get plugin option values
		 *
		 * Get value for pluginoption saved in wp_options. If option is not found in $wpdb->options the default value
		 * for that option is returned. Option values once taken from $wpdb->options are cached to prevent execution
		 * of the same query multiple times during teh processing of a request.
		 *
		 * @param array $option OPTION_ARRAY (use class constants).
		 *
		 * @return mixed Value for OPTION_ARRAY ($option): (1) cached value (2) wp_options value (3) default value.
		 * @since   1.0.0
		 *
		 */
		public static function get_option( $option ) {

			if ( isset( self::$option_cache[ $option[0] ] ) ) {
				return self::$option_cache[ $option[0] ]; // Re-use cached value.
			}

			$option_value = get_option( $option[0] );
			if ( ! $option_value ) {
				// Option not found in wp_options: save default value for re-use.
				self::$option_cache[ $option[0] ] = $option[1];
			} else {
				// Option found in wp_options: save for re-use.
				self::$option_cache[ $option[0] ] = $option_value;
			}

			return self::$option_cache[ $option[0] ]; // Return saved value.

		}

		/**
		 * Delete all plugin options from table wp_options
		 *
		 * @since   1.0.0
		 */
		public static function clear_all_options() {

			global $wpdb;

			$wpdb->query(
				"
				DELETE FROM wp_options
				WHERE option_name LIKE 'wpda_%'
			"
			); // db call ok; no-cache ok.

			self::$option_cache = array(); // Reset cache.

		}

		/**
		 * Load all WordPress tables
		 *
		 * @since   1.1.0
		 */
		public static function load_wp_tables() {

			if ( 0 === count( self::$wp_tables ) ) {//phpcs:ignore - 8.1 proof
				try {
					global $wpdb;

					if ( ! is_multisite() ) {
						foreach ( $wpdb->tables( 'all', true ) as $table ) {
							self::$wp_tables[ $table ] = $table;
						}
					} else {
						$query = "select blog_id from {$wpdb->blogs}";
						$blogs = $wpdb->get_results( $query, 'ARRAY_N' );
						foreach ( $blogs as $blog ) {
							foreach ( $wpdb->tables( $blog === reset( $blogs ) ? 'all' : 'blog', true, $blog[0] ) as $table ) {//phpcs:ignore - 8.1 proof
								self::$wp_tables[ $table ] = $table;
							}
						}
					}

					return true;
				} catch ( \Exception $e ) {
					wp_die( 'ERROR: ' . $e->getMessage() );
				}
			}

		}

		/**
		 * Checks if a table is a WordPress table
		 *
		 * @param string $table_name Table name.
		 *
		 * @return bool TRUE = WordPress table
		 * @since   1.1.0
		 *
		 */
		public static function is_wp_table( $table_name ) {

			self::load_wp_tables();

			if ( 0 === count( self::$wp_tables ) ) {//phpcs:ignore - 8.1 proof
				return false;
			}

			return isset( self::$wp_tables[ $table_name ] );

		}

		/**
		 * List containing all WordPress tables
		 *
		 * @return array
		 * @since   1.1.0
		 *
		 */
		public static function get_wp_tables() {

			self::load_wp_tables();

			if ( 0 === count( self::$wp_tables ) ) {//phpcs:ignore - 8.1 proof
				wp_die( __( 'ERROR: No WordPress table found', 'wp-data-access' ) );
			}

			return self::$wp_tables;

		}

		/**
		 * Returns a list of all plugin tables
		 *
		 * @return array List of plugin tables
		 * @since   1.0.0
		 *
		 */
		public static function get_wpda_tables() {

			global $wpdb;
			$wpda_tables = array();

			foreach ( self::WPDA_TABLES as $key => $value ) {
				$wpda_tables[] = $wpdb->prefix . $key;
			}

			return $wpda_tables;

		}

		/**
		 * Check if repository column represents a schema name
		 *
		 * @param string $table_name Table name
		 * @param string $column_name Column name
		 *
		 * @return bool
		 */
		public static function column_is_schema_name( $table_name, $column_name ) {
			if ( 0 === count( self::$wpda_tables ) ) {//phpcs:ignore - 8.1 proof
				// Cache schema names
				global $wpdb;
				self::$wpda_tables[$wpdb->prefix . 'wpda_media.media_schema_name' ] = true;
				self::$wpda_tables[$wpdb->prefix . 'wpda_project_page.page_schema_name' ] = true;
				self::$wpda_tables[$wpdb->prefix . 'wpda_menus.menu_schema_name' ] = true;
				self::$wpda_tables[$wpdb->prefix . 'wpda_project_table.wpda_schema_name' ] = true;
				self::$wpda_tables[$wpdb->prefix . 'wpda_publisher.pub_schema_name' ] = true;
				self::$wpda_tables[$wpdb->prefix . 'wpda_table_design.wpda_schema_name' ] = true;
				self::$wpda_tables[$wpdb->prefix . 'wpda_table_settings.wpda_schema_name' ] = true;
			}

			return isset( self::$wpda_tables[ "{$table_name}.{$column_name}" ] );
		}

		/**
		 * Return the default value for a plugin option
		 *
		 * @param array $option OPTION_ARRAY (use class constants).
		 *
		 * @return mixed Default value for OPTION_ARRAY ($option).
		 * @since   1.0.0
		 *
		 */
		public static function get_option_default( $option ) {

			return $option[1]; // Default option value.

		}

		/**
		 * Checks if table is plugin table
		 *
		 * NOTE
		 * Variable $phpdoc_supported_solution is a temporary variable that does not add any functionality to this
		 * function. It only serves the purpose to get class WPDA in the documentation!!! If the isset statement in
		 * the return is performed directly on self::WPDA_TABLES, class WPDA will not appear in the phpdoc generated
		 * documentation. To avoid class WPDA to be undocumented, we use $phpdoc_supported_solution.
		 *
		 * @param string $real_table_name Table name to be checked.
		 *
		 * @return bool TRUE = $table_name is a WPDA table, FALSE = $table_name is not a WPDA table.
		 * @since   1.0.0
		 *
		 */
		public static function is_wpda_table( $real_table_name ) {
			if ( null === $real_table_name ) {
				return false;
			}

			global $wpdb;

			$phpdoc_supported_solution = self::WPDA_TABLES; // DO NOT DELETE THIS TO MAKE THE CODE SIMPLER!!! (read).

			return
				(
					isset(
						$phpdoc_supported_solution[ substr( $real_table_name, strlen( $wpdb->prefix ) ) ]
					) && (
						$wpdb->prefix === substr( $real_table_name, 0, strlen( $wpdb->prefix ) )
					)
				);
		}

		/**
		 * Save plugin option
		 *
		 * Saves a plugin option in $wpdb->options.
		 *
		 * @param array $option OPTION_ARRAYS (use class constants).
		 * @param mixed $value Value to be saved for $option. If null set to default.
		 *
		 * @since   1.0.0
		 *
		 */
		public static function set_option( $option, $value = null ) {

			try {

				if ( is_null( $value ) ) {
					$option_value = $option[1]; // Set option to default.
				} else {
					$option_value = $value; // Set option value.
				}

				update_option( $option[0], $option_value ); // Save option value in wp_options.

				self::$option_cache[ $option[0] ] = $option_value; // Save for re-use.

			} catch ( \Exception $e ) {

				die( 'ERROR: ' . esc_html( $e->errorMessage() ) );

			}

		}

		/**
		 * Simplify data type for simple forms
		 *
		 * Data types used in plugin a re simplified for simple form usage.
		 *
		 * @param string $arg Data type as known to the MySQL database.
		 *
		 * @return string Simplified data type (mainly used to recognize when to use quotes).
		 * @since   1.0.0
		 *
		 * @see \WPDataAccess\Simple_Form\WPDA_Simple_Form
		 *
		 */
		public static function get_type( $arg ) {
			$type = trim( str_replace( 'unsigned', '', (string) $arg ) );
			if ( false !== ( $index = strpos( $type, '(' ) ) ) {
				$type = substr( $type, 0, $index );
			}
			switch ( $type ) {
				case 'tinyint':
				case 'smallint':
				case 'mediumint':
				case 'int':
				case 'bigint':
				case 'float':
				case 'double':
				case 'decimal':
				case 'year':
				case 'number': // DataTables support
					return 'number';
				case 'date':
				case 'datetime':
				case 'timestamp':
					return 'date';
				case 'time':
					return 'time';
				case 'enum':
					return 'enum';
				case 'set':
					return 'set';
				default:
					return 'string';
			}
		}

		public static function get_sb_type( $arg ) {
			$type = trim( str_replace( 'unsigned', '', (string) $arg ) );
			if ( false !== ( $index = strpos( $type, '(' ) ) ) {
				$type = substr( $type, 0, $index );
			}
			switch ( $type ) {
				case 'tinyint':
				case 'smallint':
				case 'mediumint':
				case 'int':
				case 'bigint':
				case 'float':
				case 'double':
				case 'decimal':
				case 'year':
					return 'num';
				case 'date':
				case 'datetime':
				case 'timestamp':
					return 'date';
				default:
					return 'string';
			}
		}

		/**
		 * Insert a value or key/value pair after a specific key in an array.  If key doesn't exist, value is appended
		 * to the end of the array.
		 *
		 * Source: https://gist.github.com/wpscholar/0deadce1bbfa4adb4e4c
		 *
		 * @param array $array
		 * @param string $key
		 * @param array $new
		 *
		 * @return array
		 */
		public static function array_insert_after( $array, $key, $new ) {
			$keys = array_keys( (array) $array ); //phpcs:ignore - 8.1 proof
			$index = array_search( $key, $keys ); //phpcs:ignore - 8.1 proof
			$pos = false === $index ? count( $array ) : $index + 1; //phpcs:ignore - 8.1 proof

			return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) ); //phpcs:ignore - 8.1 proof
		}

		/**
		 * Log a message in the database
		 *
		 * Use this method to log messages to the database.
		 *
		 * NOTE Don't use $wpdb->insert! You'll miss a lot of information...
		 *
		 * @param $log_id   string Id to identify/find logged data.
		 * @param $log_type string Possible values: 'FATAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'
		 * @param $log_msg  string Any text (max length 4096kb).
		 *
		 * @since   2.0.7
		 *
		 */
		public static function log( $log_id, $log_type, $log_msg ) {
			global $wpdb;

			$sql =
				$wpdb->prepare(
					'INSERT INTO ' . $wpdb->prefix .
					'wpda_logging (log_time, log_id, log_type, log_msg) VALUES (now(), %s, %s, %s)'
					, $log_id
					, $log_type
					, $log_msg
				);
			$wpdb->query( $sql );

		}

		/**
		 * Get user role
		 *
		 * @return mixed Current user roles or FALSE if not logged in.
		 * @since   2.0.8
		 *
		 */
		public static function get_current_user_roles() {
			if ( is_user_logged_in() ) {
				$user = wp_get_current_user();
				if ( ! is_array( $user->roles ) ) {
					return ( array ) $user->roles;
				} else {
					return $user->roles;
				}
			} else {
				return false;
			}
		}

		/**
		 * Get user capability
		 *
		 * @return mixed Current users first capability or FALSE if not logged in.
		 * @since   2.0.8
		 *
		 */
		public static function get_current_user_capability() {
			if ( is_user_logged_in() ) {
				$user    = wp_get_current_user();
				$allcaps = array();
				foreach ( $user->allcaps as $key => $val ) {
					array_push( $allcaps, $key );//phpcs:ignore - 8.1 proof
				}

				return $allcaps[0];
			} else {
				return false;
			}
		}

		/**
		 * Convert memory value to integer.
		 *
		 * @param $memory_value string Memory value (eg 128M)
		 *
		 * @return integer Converted value in decimal
		 * @since   2.0.8
		 *
		 */
		public static function convert_memory_to_decimal( $memory_value ) {
			if ( preg_match( '/^(\d+)(.)$/', $memory_value, $matches ) ) {
				if ( $matches[2] == 'G' ) {
					return $matches[1] * 1024 * 1024 * 1024;
				} else if ( $matches[2] == 'M' ) {
					return $matches[1] * 1024 * 1024;
				} else if ( $matches[2] == 'K' ) {
					return $matches[1] * 1024;
				}
			}
		}

		public static function get_current_user_id() {
			global $current_user;
			if ( isset( $current_user->ID ) ) {
				return $current_user->ID;
			} else {
				if ( function_exists('wp_get_current_user') ) {
					$wp_user = wp_get_current_user();
					if ( isset( $wp_user->ID ) ) {
						return $wp_user->ID;
					} else {
						return -1;
					}
				} else {
					return -1;
				}
			}
		}

		public static function get_current_user_login() {
			global $current_user;
			if ( isset( $current_user->user_login ) ) {
				return $current_user->user_login;
			} else {
				if ( function_exists('wp_get_current_user') ) {
					$wp_user = wp_get_current_user();
					if ( isset( $wp_user->data->user_login ) ) {
						return $wp_user->data->user_login;
					} else {
						return 'anonymous';
					}
				} else {
					return 'anonymous';
				}
			}
		}

		public static function get_current_user_email() {
			global $current_user;
			if ( isset( $current_user->user_email ) ) {
				return $current_user->user_email;
			} else {
				$wp_user = wp_get_current_user();
				if ( isset( $wp_user->data->user_email ) ) {
					return $wp_user->data->user_email;
				} else {
					return 'anonymous';
				}
			}
		}

		/**
		 * Replaces environment variables in where clause with appropriate values
		 *
		 * @param $where_clause
		 *
		 * @return string
		 */
		public static function substitute_environment_vars( $where_clause ) {
			if ( strpos( $where_clause, '$$USERID$$' ) ) {
				$user_id      = WPDA::get_current_user_id();
				$where_clause = str_replace( '$$USERID$$', $user_id, $where_clause );
			}

			if ( strpos( $where_clause, '"$$USER$$"' ) ) {
				$user_login   = WPDA::get_current_user_login();
				$where_clause = str_replace( '"$$USER$$"', '"' . $user_login . '"', $where_clause );
			}
			if ( strpos( $where_clause, "'" . '$$USER$$' . "'" ) ) {
				$user_login   = WPDA::get_current_user_login();
				$where_clause = str_replace( "'" . '$$USER$$' . "'", "'" . $user_login . "'", $where_clause );
			}
			if ( strpos( $where_clause, '$$USER$$' ) ) {
				$user_login   = WPDA::get_current_user_login();
				$where_clause = str_replace( '$$USER$$', "'" . $user_login . "'", $where_clause );
			}

			if ( strpos( $where_clause, '"$$EMAIL$$"' ) ) {
				$user_email      = WPDA::get_current_user_email();
				$where_clause = str_replace( '"$$EMAIL$$"', '"' . $user_email . '"', $where_clause );
			}
			if ( strpos( $where_clause, "'" . '$$EMAIL$$' . "'" ) ) {
				$user_email      = WPDA::get_current_user_email();
				$where_clause = str_replace( "'" . '$$EMAIL$$' . "'", "'" . $user_email . "'", $where_clause );
			}
			if ( strpos( $where_clause, '$$EMAIL$$' ) ) {
				$user_email      = WPDA::get_current_user_email();
				$where_clause = str_replace( '$$EMAIL$$', "'" . $user_email . "'", $where_clause );
			}

			return $where_clause;
		}

		/**
		 * Where clause construction (use filter if applied)
		 *
		 * Param $columns = associative array containing the following column info taken from the data dictionary:
		 *  $columns['column_name']    => information_schema.tables.column_name
		 *  $columns['data_type']      => information_schema.tables.data_type
		 * 	$columns['extra']          => information_schema.tables.extra
		 * 	$columns['column_type'     => information_schema.tables.column_type
		 * 	$columns['is_nullable'     => information_schema.tables.is_nullable
		 * 	$columns['column_default'] => information_schema.tables.column_default
		 *
		 * @param string $schema_name Schema name (optional)
		 * @param string $table_name Table name (optional)
		 * @param string $columns Array containing table columns
		 * @param string $search Search string entered by user
		 * @param boolean $is_case_sensitive Case-sensitive search (default = false)
		 * @param boolean $is_dt_request Always skip filter for DataTable requests (default = false)
		 *
		 * @return string Where clause between ()
		 */
		public static function construct_where_clause(
			$schema_name,
			$table_name,
			$columns,
			$search,
			$is_case_sensitive = false,
			$is_dt_request = false
		) {
			$where_search_args = self::add_wpda_search_args( $columns );

			if (
				! $is_case_sensitive &&
				! $is_dt_request &&
				has_filter('wpda_construct_where_clause')
			) {
				global $wpda_search_args;
				$wpda_search_args = $where_search_args;

				// Use search filter
				$filter = apply_filters(
					'wpda_construct_where_clause',
					'',
					$schema_name,
					$table_name,
					$columns,
					$search
				);
				if ( null !== $filter ) {
					if ( '' === $where_search_args ) {
						return $filter;
					} else {
						if ( '' === $filter ) {
							return $where_search_args;
						} else {
							return "{$filter} and {$where_search_args}";
						}
					}
				}
			}

			// Define case-sensitive search
			$case_sensitive = $is_case_sensitive ? 'binary' : '';

			// Default search behaviour
			if ( '' === $search || null === $search || ! is_array( $columns ) ) {
				return $where_search_args;
			}

			global $wpdb;
			$where_columns = array();

			foreach ( $columns as $column ) {
				if ( 'string' === WPDA::get_type( $column['data_type'] ) ) {
					$where_columns[] = $wpdb->prepare( "`" . str_replace( '`', '', $column['column_name'] ) . "` like {$case_sensitive} '%s'", '%' . esc_sql( $search ) . '%' ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
				}

				if ( $is_dt_request ) {
					// Handle numeric and date queries for DataTables.
					if ( 'number' === WPDA::get_type( $column['data_type'] ) && ( is_numeric( $search ) ) ) {
						$where_columns[] = $wpdb->prepare( "`" . str_replace( '`', '', $column['column_name'] ) . "` = '%s'", esc_sql( $search ) ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
					} elseif ( 'date' === WPDA::get_type( $column['data_type'] ) ) {
						$where_columns[] = $wpdb->prepare( "`" . str_replace( '`', '', $column['column_name'] ) . "` like '%s'", esc_sql( $search ) . '%' ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
					}
				}
			}

			if ( 0 === count( $where_columns ) ) {//phpcs:ignore - 8.1 proof
				return '' === $where_search_args ? ' (1=2) ' : $where_search_args;
			}

			if ( '' === $where_search_args ) {
				return ' (' . implode( ' or ', $where_columns ) . ') ';
			} else {
				return ' (' . implode( ' or ', $where_columns ) . ') ' . "and {$where_search_args}";
			}
		}

		public static function add_wpda_search_args( $columns ) {
			$where_columns = array();

			if ( is_array( $columns ) ) {
				global $wpdb;
				$request = array_change_key_case( $_REQUEST ); //phpcs:ignore - 8.1 proof
				foreach ( $columns as $column ) {
					$column_name     = str_replace( '`', '', $column['column_name'] );
					$column_name_lwr = strtolower( $column_name );
					if ( isset( $request["wpda_search_column_{$column_name_lwr}"] ) ) {
						if ( is_array( $request["wpda_search_column_{$column_name_lwr}"] ) ) {
							// Handle multiple values for same column with OR
							$where_columns_arr = array();
							foreach ( $request["wpda_search_column_{$column_name_lwr}"] as $value ) {
								$column_date_type = $column['data_type'];
								$column_value     = wp_strip_all_tags( wp_unslash( $value ) ); // input var okay.
								if ( '' !== $column_value ) {
									if ( 'string' === WPDA::get_type( $column_date_type ) ) {
										$where_columns_arr[] = $wpdb->prepare( "`{$column_name}` like '%s'", esc_sql( $column_value ) ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
									} elseif ( 'number' === WPDA::get_type( $column_date_type ) ) {
										$where_columns_arr[] = $wpdb->prepare( "`{$column_name}` = '%d'", esc_sql( $column_value ) ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
									}
								}
							}
							if ( count( $where_columns_arr ) > 0 ) {//phpcs:ignore - 8.1 proof
								$where_columns[] = ' (' . implode( ' or ', $where_columns_arr ) . ') ';//phpcs:ignore - 8.1 proof
							}
						} else {
							// Handle single value
							$column_date_type = $column['data_type'];
							$column_value     = wp_strip_all_tags( wp_unslash( $request[ "wpda_search_column_{$column_name_lwr}" ] ) );
							if ( '' !== $column_value ) {
								if ( 'string' === WPDA::get_type( $column_date_type ) ) {
									$where_columns[] = $wpdb->prepare( "`{$column_name}` like '%s'", esc_sql( $column_value ) ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
								} elseif ( 'number' === WPDA::get_type( $column_date_type ) ) {
									$where_columns[] = $wpdb->prepare( "`{$column_name}` = '%d'", esc_sql( $column_value ) ); // phpcs:ignore Standard.Category.SniffName.ErrorCode
								}
							}
						}
					}
				}
			}

			if ( 0 === count( $where_columns ) ) {//phpcs:ignore - 8.1 proof
				return '';
			} else {
				$operator =
					isset( $_REQUEST['wpda_search_column_operator'] ) &&
					'or' === strtolower( $_REQUEST['wpda_search_column_operator'] )
						? 'or' : 'and';
				return ' (' . implode( " $operator ", $where_columns ) . ') ';
			}
		}

		public static function schema_disabled( $schema_name ) {
			if ( 'rdb:' === substr( $schema_name, 0, 4) ) {
				$rdb = WPDADB::get_remote_database( $schema_name, true );
				if ( false === $rdb ) {
					return false;
				}
				return true === $rdb[ 'disabled' ];
			}
			return false;
		}

		/**
		 * Check if database schema exists (local and remote databases)
		 *
		 * @param string $schema_name Database schema
		 *
		 * @return bool Exists?
		 */
		public static function schema_exists( $schema_name ) {
			if ( self::schema_disabled( $schema_name ) ) {
				return false;
			}

			$wpdadb = WPDADB::get_db_connection( $schema_name );
			if ( null === $wpdadb ) {
				return false;
			}
			if ( method_exists( $wpdadb, 'is_connected' ) ) {
				if ( ! $wpdadb->is_connected() ) {
					$msg = new WPDA_Message_Box(
						[
							'message_text'           => __( "Remote database '{$schema_name}' not available [check connection: Settings > WP Data Access]", 'wp-data-access' ),
							'message_type'           => 'error',
							'message_is_dismissible' => false,
						]
					);
					$msg->box();

					return false;
				}
			}

			$wpdadb->query(
				$wpdadb->prepare( '
							SELECT TRUE FROM information_schema.schemata WHERE schema_name = %s
						',
					[
						$wpdadb->dbname,
					]
				)
			); // db call ok; no-cache ok.
			$wpdadb->get_results(); // phpcs:ignore Standard.Category.SniffName.ErrorCode

			return 1 === $wpdadb->num_rows;
		}

		public static function get_user_default_scheme() {
			$default_databases = get_option('wpda_default_database');
			if ( false !== $default_databases ) {
				$user_id = get_current_user_id();
				if (
					$user_id > 0 &&
					isset( $default_databases[ $user_id ] ) &&
					self::schema_exists( $default_databases[ $user_id ] )
				) {
					return $default_databases[ $user_id ];
				}
			}

			global $wpdb;
			return $wpdb->dbname;
		}

		public static function shortcode_popup() {
			?>
			<script type="text/javascript">
				jQuery(function() {
					var clipboard = new ClipboardJS('.wpda_shortcode_clipboard');
				});
			</script>
			<style type="text/css">
				.wpda_shortcode_content {
					padding: 0 20px;
				}
                .wpda_shortcode_text {
                    text-align: center;
                    font-size: 105%;
					white-space: nowrap;
                }
                .wpda_shortcode_buttons {
                    text-align: center;
                }
                .button.wpda_shortcode_button {
                    width: 100px !important;
                }
				.wpda_shortcode_link {
					text-decoration: none;
					font-weight: bold;
				}
			</style>
			<?php
		}

		public static function is_editing_post() {
			global $pagenow;
			if ( $pagenow === 'post.php' || $pagenow === 'edit.php'  || $pagenow === 'post-new.php' ) {
				// Editing post in classic editor
				return '';
			}

			if ( isset( $_SERVER["CONTENT_TYPE"] ) && 'application/json' === $_SERVER["CONTENT_TYPE"] ) {
				// Editing post in Gutenberg editor
				return null;
			}

			return false;
		}

		/**
		 * Get all available post types.
		 *
		 * @return array
		 */
		public static function get_post_types() {
			global $wpdb;
			$rows = $wpdb->get_results(
				"select distinct post_type from {$wpdb->posts}",
				'ARRAY_N'
			);

			$cpts = array();
			foreach ( $rows as $row ) {
				$cpts[] = $row[0];
			}

			return $cpts;
		}

		public static function get_table_engine( $schema_name, $table_name ) {
			$table_info = WPDA::get_table_values( $schema_name, $table_name );
			//phpcs:ignore - 8.1 proof
			return (
				1 === count( $table_info ) &&
				'connect' === strtolower( $table_info[0]['engine'] )
			) ? 'connect' : '';
		}

		public static function get_table_values( $schema_name, $table_name ) {
			$wpdadb = WPDADB::get_db_connection( $schema_name );
			return $wpdadb->get_results(
				$wpdadb->prepare(
					"
						select engine as engine,
							   table_rows as table_rows,
							   table_type as table_type
						 from  information_schema.tables
						where  table_schema = %s
						  and  table_name = %s
					",
					[
						$wpdadb->dbname,
						$table_name
					]
				),
				'ARRAY_A'
			);
		}

		/**
		 * Get estimated number of rows in a table
		 *
		 * @param $schema_name Schema name
		 * @param $table_name Table name
			 * @param $wpda_table_settings Table settings (query WPDA_Table_Settings_Model)
		 *
		 * @return int row count estimate or -1 if no estimate available
		 */
		public static function get_row_count_estimate( $schema_name, $table_name, $wpda_table_settings ) {
			$row_count     = null;
			$is_estimate   = null;
			$do_real_count = null;
			$table_info    = self::get_table_values( $schema_name, $table_name );
			if ( 1 === count( $table_info ) ) {//phpcs:ignore - 8.1 proof
				$row_count  = $table_info[0]['table_rows'];

				$system_row_count_estimate    = null;
				$row_count_estimate_value     = null;
				$row_count_estimate_value_hard = null;

				if ( isset( $wpda_table_settings->table_settings->row_count_estimate ) ) {
					$system_row_count_estimate = $wpda_table_settings->table_settings->row_count_estimate;
				}

				if ( isset( $wpda_table_settings->table_settings->row_count_estimate_value ) ) {
					$row_count_estimate_value = $wpda_table_settings->table_settings->row_count_estimate_value;
				}
				if ( isset( $wpda_table_settings->table_settings->row_count_estimate_value_hard ) ) {
					$row_count_estimate_value_hard = $wpda_table_settings->table_settings->row_count_estimate_value_hard;
				}

				if (
					'federated' === strtolower( (string) $table_info[0]['engine'] ) ||
					'innodb' === strtolower( (string) $table_info[0]['engine'] ) ||
					stripos( strtolower( (string) $table_info[0]['table_type'] ), 'view' ) !== false
				) {
					// Handle InnoDB tables, views and system views
					$wpdadb = WPDADB::get_db_connection( $schema_name );
					$view_explain = $wpdadb->get_results(
						'explain select count(*) from `' . str_replace( '`', '', $table_name ) . '`',
						'ARRAY_A'
					);

					if ( isset( $view_explain[0]['rows'] ) ) {
						$row_count = $view_explain[0]['rows'];
					} elseif ( isset( $view_explain[0]['ROWS'] ) ) {
						$row_count = $view_explain[0]['ROWS'];
					}

					if ( true === $system_row_count_estimate ) {
						if ( 'hard' === $row_count_estimate_value && null !== $row_count_estimate_value_hard ) {
							$row_count = $row_count_estimate_value_hard;
						}
						$is_estimate   = true;
						$do_real_count = false;
					} elseif ( false === $system_row_count_estimate ) {
						$is_estimate   = false;
						$do_real_count = true;
					} else {
						if ( intval( $row_count ) > intval( WPDA::get_option( WPDA::OPTION_BE_INNODB_COUNT ) ) ) {
							$is_estimate   = true;
							$do_real_count = false;
						} else {
							$is_estimate   = false;
							$do_real_count = true;
						}
					}
				} elseif ( 'connect' === strtolower( $table_info[0]['engine'] ) ) {
					$getpk = WPDA_List_Columns_Cache::get_list_columns( $schema_name, $table_name );
					$pk    = $getpk->get_table_primary_key();
					if ( is_array( $pk ) && count( $pk ) > 0 ) {//phpcs:ignore - 8.1 proof
						$sql_rowcount_sql =
							'select count(*) from `' . str_replace( '`', '', $table_name ) .
							'` where `' . $pk[0] . '` not in ' .
								'(select null from `' . str_replace( '`', '', $table_name ) . '` where 1=2)';
						$wpdadb = WPDADB::get_db_connection( $schema_name );
						$sql_rowcount  = $wpdadb->get_results( $sql_rowcount_sql, 'ARRAY_N' );
						if ( count( $sql_rowcount ) === 1 ) {//phpcs:ignore - 8.1 proof
							$row_count     = $sql_rowcount[0][0];
							$is_estimate   = false;
							$do_real_count = false;
						} else {
							$is_estimate   = false;
							$do_real_count = true;
						}
					} else {
						$is_estimate   = false;
						$do_real_count = true;
					}
				} else {
					// Handle other table types (MyISAM and NDB)
					$is_estimate   = false;
					$do_real_count = false;
				}
			}

			return array(
				'row_count'     => $row_count,
				'is_estimate'   => $is_estimate,
				'do_real_count' => $do_real_count
			);
		}

		public static function validate_names( $schema_name, $table_name, $column_names = null ) {
			if ( ! self::validate_name( $table_name ) ) {
				return self::validate_name_failed();
			}

			if ( 'rdb:' === substr( $schema_name, 0, 4 ) ) {
				$wpdadb = WPDADB::get_db_connection( $schema_name );
				if ( $wpdadb === null ) {
					return self::validate_name_np();
				} else {
					if ( ! self::validate_name( $wpdadb->dbname ) ) {
						return self::validate_name_failed();
					}
				}
			} else {
				// Added prefix to schema validation: allowing database names to start with a number
				if ( ! self::validate_name( 'prefix_' . $schema_name ) ) {
					return self::validate_name_failed();
				}
			}

			if ( null === $column_names ) {
				$columns      = WPDA_Dictionary_Lists::get_table_columns( $table_name, $schema_name );
				$column_names = array_column( $columns, 'column_name' ); //phpcs:ignore - 8.1 proof
			}
			if ( count( $column_names ) === 0 ) {//phpcs:ignore - 8.1 proof
				return self::validate_name_np();
			}

			$is_valid = true;
			foreach ( $column_names as $column_name ) {
				if ( ! self::validate_name( $column_name ) ) {
					$is_valid = false;
					break;
				}
			}

			return $is_valid ? '' : self::validate_name_failed();
		}

		public static function validate_name( $item_name ) {
			if (
				! ctype_alpha( substr( $item_name, 0, 1 ) ) ||
				! preg_match("/^[_a-zA-Z0-9]+$/", $item_name)
			){
				return false;
			}

			return true;
		}

		public static function validate_name_failed() {
			$title   = __( 'Schema, table or column name(s) restricting plugin features (click to read more and fix)', 'wp-data-access' );
			$warning = "
				<a href='https://wpdataaccess.com/docs/data-explorer/naming-conventions/' target='_blank' style='text-decoration:none'>
					<span class='dashicons dashicons-flag wpda_tooltip' style='color:red;padding-left:5px' title='$title'></span>
				</a>";
			return $warning;
		}

		public static function validate_name_np() {
			$title   = __( 'Schema, table or column name validation not possible (click to read more and fix)', 'wp-data-access' );
			$warning = "
				<a href='https://wpdataaccess.com/docs/data-explorer/naming-conventions/' target='_blank'>
					<span class='dashicons dashicons-warning wpda_tooltip' style='padding-left:5px' title='$title'></span>
				</a>";
			return $warning;
		}

		public static function get_dbms_var( $var_name = null, $schema_name = null ) {
			if ( null === $schema_name ) {
				global $wpdb;
				$wpdadb = $wpdb;
			} else {
				$wpdadb = WPDADB::get_db_connection( $schema_name );
			}

			if ( null === $wpdadb ) {
				return null;
			}

			if ( null !== $var_name ) {
				$var_value = $wpdadb->get_results( "SHOW VARIABLES LIKE '$var_name'", 'ARRAY_N' );
				if ( is_array( $var_value ) && isset( $var_value[0][1] ) ) {
					return $var_value[0][1];
				} else {
					return null;
				}
			} else {
				return $wpdadb->get_results( 'SHOW VARIABLES', 'ARRAY_N' );
			}
		}

		public static function get_dbms_global( $global_name = null, $schema_name = null ) {
			if ( null === $schema_name ) {
				global $wpdb;
				$wpdadb = $wpdb;
			} else {
				$wpdadb = WPDADB::get_db_connection( $schema_name );
			}

			if ( null === $wpdadb ) {
				return null;
			}

			if ( null !== $global_name ) {
				$global_value = $wpdadb->get_results( "SHOW GLOBAL STATUS LIKE '$global_name'", 'ARRAY_N' );
				if ( is_array( $global_value ) && isset( $global_value[0][1] ) ) {
					return $global_value[0][1];
				} else {
					return null;
				}
			} else {
				return $wpdadb->get_results( 'SHOW GLOBAL STATUS', 'ARRAY_N' );
			}
		}

		public static function secondsToTime( $seconds ) {
			$dtF = new \DateTime('@0');
			$dtT = new \DateTime("@$seconds");
			return $dtF->diff( $dtT )->format('%a days, %h hours, %i minutes and %s seconds');
		}

		public static function get_sonce_token() {
			$encrypt_method = 'AES-256-CBC';
			$key            = hash( 'sha256', WPDA::get_option( WPDA::OPTION_PLUGIN_SECRET_KEY ) );
			$iv             = substr( hash( 'sha256', WPDA::get_option( WPDA::OPTION_PLUGIN_SECRET_IV ) ), 0, 16 );

			return base64_encode( openssl_encrypt( WPDA::get_option( WPDA::OPTION_PLUGIN_SONCE_SEED ) . $_SERVER['REMOTE_ADDR'], $encrypt_method, $key, 0, $iv ) );
		}

		public static function wpda_create_sonce( $action = 'undefined' ) {
			$token = self::get_sonce_token();
			$i     = wp_nonce_tick();

			return substr( wp_hash( $i . '|' . $action . '|' . $token, 'nonce' ), -12, 10 );
		}

		public static function wpda_verify_sonce( $sonce, $action = 'undefined' ) {
			$expected = WPDA::wpda_create_sonce($action);

			return hash_equals( $expected , $sonce );
		}

		public static function get_plugin_upload_dir() {
			$uploads = wp_upload_dir();

			return $uploads['basedir'] . DIRECTORY_SEPARATOR . 'wp-data-access' . DIRECTORY_SEPARATOR;
		}

		public static function wpda_create_content_folder() {
			$upload_dir = WPDA::get_plugin_upload_dir();
			if ( ! file_exists( $upload_dir ) ) {
				mkdir( $upload_dir, 0755, true );
				$fw = fopen( $upload_dir . ".htaccess", 'w' );
				if ( false !== $fw ) {
					fwrite( $fw, "IndexIgnore *" );
				}
				fclose( $fw );
			}
		}

		public static function wpda_delete_content_folder() {
			$upload_dir = WPDA::get_plugin_upload_dir();
			if ( file_exists( $upload_dir ) ) {
				$files = glob( $upload_dir . '*', GLOB_MARK );
				foreach ( $files as $file ) {
					unlink( $file );
				}
				unlink( $upload_dir . '.htaccess'  );

				rmdir( $upload_dir );
			}
		}

		public static function sent_header( $content_type, $cors = null, $attachment = null ) {
			if ( null !== $cors ) {
				header( "Access-Control-Allow-Origin: {$cors}" );
			}

			if ( null !== $attachment ) {
				header("Content-Disposition: attachment; filename={$attachment}");
			}

			header( "Content-type: {$content_type}" );
			header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
			header( 'Cache-Control: post-check=0, pre-check=0', false );
			header( 'Pragma: no-cache' );
			header( 'Expires: 0' );
		}

		public static function sent_msg( $status, $msg ) {
			echo json_encode(
				array(
					'status' => $status,
					'msg'    => $msg,
				), true
			); // phpcs:ignore - 8.1 proof
		}

		public static function is_post() {
			global $post;
			return 'post' === get_post_type( $post );
		}

		public static function is_page() {
			global $post;
			return 'page' === get_post_type( $post );
		}

		public static function can_manage() {
			$user_id = get_current_user_id();

			$wpda_hide_manage_link = get_option( 'wpda_hide_manage_link' );
			if ( is_array( $wpda_hide_manage_link ) ) {
				$wpda_hide_manage_list = array_flip( $wpda_hide_manage_link ); //phpcs:ignore - 8.1 proof
			} else {
				$wpda_hide_manage_list = array();
			}

			return ! isset( $wpda_hide_manage_list[ $user_id ] );
		}

		/**
		 * Remove backticks from string
		 *
		 * @param string $s Subject.
		 * @return array|string|string[]
		 */
		public static function remove_backticks( $s ) {
			return str_replace( '`', '', $s );
		}

		/**
		 * Sanitize array:
		 * uses sanitize_text_field on all array elements
		 * supports multidimensional arrays
		 *
		 * @param mixed $input Any value (initial value is supposed to be an array).
		 * @param array $is_textarea List of textarea fields.
		 * @return array|mixed
		 */
		public static function sanitize_text_field_array( $input, $is_textarea = array() ) {
			return
				is_array( $input ) ?
					array_map(
						function( $element ) use ( $is_textarea ) { return WPDA::sanitize_text_field_array( $element, $is_textarea ); },
						$input
					) :
					(
						is_scalar( $input ) ?
							( ! isset( $is_textarea[ $input ] ) ? sanitize_text_field( wp_unslash( $input ) ) : sanitize_textarea_field( $input ) ) :
							$input
					);
		}

		/**
		 * Get server IP address
		 *
		 * @return mixed|string
		 */
		public static function get_server_address() {
			if ( isset( $_SERVER['SERVER_ADDR'] ) ) {
				return $_SERVER['SERVER_ADDR'];
			} elseif ( isset( $_SERVER['LOCAL_ADDR'] ) ) {
				return $_SERVER['LOCAL_ADDR'];
			} else {
				return 'UNKNOWN';
			}
		}

		public static function convert_default_value( $item_default_value ) {
			switch( $item_default_value ) {
				case '$$USERID$$':
					return WPDA::get_current_user_id();
				case '$$USER$$':
					return WPDA::get_current_user_login();
				case '$$EMAIL$$':
					return WPDA::get_current_user_email();
				case '$$NOW$$':
				case '$$NOWDT$$':
					global $wpdb;
					$now_db       = $wpdb->get_var( 'select now()' );
					$db_format    = WPDA::DB_DATETIME_FORMAT;
					$convert_date = \DateTime::createFromFormat( $db_format, $now_db );
					if ( false !== $convert_date ) {
						$date_format = WPDA::get_option( WPDA::OPTION_PLUGIN_DATE_FORMAT );
						$time_format = WPDA::get_option( WPDA::OPTION_PLUGIN_TIME_FORMAT );
						$item_format = $date_format;
						if ( '$$NOWDT$$' === $item_default_value ) {
							$item_format .= " {$time_format}";
						}
						return $convert_date->format( $item_format );
					} else {
						return '';
					}
				default:
					return $item_default_value;
			}
		}

		/**
		 * Write error to WordPress log file
		 *
		 * @param string $errmsg Error message
		 * @return void
		 */
		public static function wpda_log_wp_error( $errmsg ) {
			$dbt = debug_backtrace();
			$clr = array_shift( $dbt );//phpcs:ignore - 8.1 proof

			error_log( "WP Data Access error in {$clr['file']}:{$clr['line']}" );
			error_log( print_r( $errmsg, true ) );
		}

		/**
		 * Get the column data types from a custom query
		 *
		 * @param $database Database (schema) name
		 * @param $query Custom query
		 * @return array|null Column data type array or null if the query does not return any columns
		 */
		public static function get_columns_from_query( $database, $query ) {
			$wpdadb = WPDADB::get_db_connection( $database );
			if ( null === $wpdadb ) {
				return null;
			}

			$wpdadb->suppress_errors( true );
			$table_name = 'custom' . mt_rand(0, time());

			// Create temporary table.
			$wpdadb->query(
				"CREATE TEMPORARY TABLE `{$table_name}` AS (select * from (" . $query . ') as t limit 0)'
			);

			if ( '' !== $wpdadb->last_error ) {
				return null;
			}

			// Get query structure.
			$rows = $wpdadb->get_results( "desc `{$table_name}`", 'ARRAY_A' );

			if ( '' !== $wpdadb->last_error ) {
				return null;
			}

			return $rows;
		}

        public static function current_user_is_admin() {
            return current_user_can( 'manage_options' );
        }

	}

}
