<?php
/**
 * Copyright (C) 2014-2025 ServMask Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Attribution: This code is part of the All-in-One WP Migration plugin, developed by
 *
 * ███████╗███████╗██████╗ ██╗   ██╗███╗   ███╗ █████╗ ███████╗██╗  ██╗
 * ██╔════╝██╔════╝██╔══██╗██║   ██║████╗ ████║██╔══██╗██╔════╝██║ ██╔╝
 * ███████╗█████╗  ██████╔╝██║   ██║██╔████╔██║███████║███████╗█████╔╝
 * ╚════██║██╔══╝  ██╔══██╗╚██╗ ██╔╝██║╚██╔╝██║██╔══██║╚════██║██╔═██╗
 * ███████║███████╗██║  ██║ ╚████╔╝ ██║ ╚═╝ ██║██║  ██║███████║██║  ██╗
 * ╚══════╝╚══════╝╚═╝  ╚═╝  ╚═══╝  ╚═╝     ╚═╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝
 */

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Kangaroos cannot jump here' );
}

class Ai1wm_Database_Utility {

	protected static $db_client = null;

	/**
	 * Set the database client instance for injection
	 *
	 * @param  Ai1wm_Database $db_client Database client instance.
	 * @return void
	 */
	public static function set_client( $db_client ) {
		self::$db_client = $db_client;
	}

	/**
	 * Get the database client instance, creating it if not set
	 *
	 * @return Ai1wm_Database
	 */
	public static function get_client() {
		if ( self::$db_client === null ) {
			return self::create_client();
		}

		return self::$db_client;
	}

	/**
	 * Create DB client to be used for DB manipulation
	 *
	 * @return Ai1wm_Database
	 */
	private static function create_client() {
		global $wpdb;

		// SQLite
		if ( $wpdb instanceof WP_SQLite_DB || $wpdb instanceof WP_SQLite_DB\wpsqlitedb ) {
			return new Ai1wm_Database_Sqlite( $wpdb );
		}

		// MariaDB
		if ( ( $server_version = $wpdb->get_var( 'SELECT VERSION()' ) ) ) {
			if ( stripos( $server_version, 'MariaDB' ) !== false ) {
				return new Ai1wm_Database_Mariadb( $wpdb );
			}
		}

		// MySQLi
		if ( PHP_MAJOR_VERSION >= 7 ) {
			return new Ai1wm_Database_Mysqli( $wpdb );
		}

		// MySQL
		if ( empty( $wpdb->use_mysqli ) ) {
			return new Ai1wm_Database_Mysql( $wpdb );
		}

		return new Ai1wm_Database_Mysqli( $wpdb );
	}

	/**
	 * Replace all occurrences of the search string with the replacement string.
	 * This function is case-sensitive.
	 *
	 * @param  string $data    Data to replace
	 * @param  array  $search  List of string we're looking to replace
	 * @param  array  $replace What we want it to be replaced with
	 * @return string          The original string with all elements replaced as needed
	 */
	public static function replace_values( $data, $search = array(), $replace = array() ) {
		return strtr( $data, array_combine( $search, $replace ) );
	}

	/**
	 * Replace serialized occurrences of the search string with the replacement string.
	 * This function is case-sensitive.
	 *
	 * @param  string $data    Data to replace
	 * @param  array  $search  List of string we're looking to replace
	 * @param  array  $replace What we want it to be replaced with
	 * @return string          The original array with all elements replaced as needed
	 */
	public static function replace_serialized_values( $data, $search = array(), $replace = array() ) {
		$pos = 0;

		$result = self::parse_serialized_values( $data, $pos, $search, $replace );
		if ( $pos !== strlen( $data ) ) {
			// Failed to parse entire data
			return strtr( $data, array_combine( $search, $replace ) );
		}

		return $result;
	}

	/**
	 * Parse serialized string and replace needed substitutions.
	 * This function is case-sensitive.
	 *
	 * @param  string  $data    Serialized data
	 * @param  integer $pos     Character position
	 * @param  array   $search  List of string we're looking to replace
	 * @param  array   $replace What we want it to be replaced with
	 * @return string           The original string with all elements replaced as needed
	 */
	public static function parse_serialized_values( $data, &$pos, $search = array(), $replace = array() ) {
		$length = strlen( $data );
		if ( $pos >= $length ) {
			return '';
		}

		$type = $data[ $pos ];
		$pos++;

		switch ( $type ) {
			case 's':
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ':' ) {
					$pos--;
					return '';
				}

				$pos++;
				$len_end = strpos( $data, ':', $pos );
				if ( $len_end === false ) {
					$pos--;
					return '';
				}

				$str_length = (int) substr( $data, $pos, $len_end - $pos );

				$pos = $len_end + 1;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '"' ) {
					$pos--;
					return '';
				}

				$pos++;
				$str = substr( $data, $pos, $str_length );

				$pos += $str_length;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '"' ) {
					$pos--;
					return '';
				}

				$pos++;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ';' ) {
					$pos--;
					return '';
				}

				$pos++;

				// If the string is a single letter, skip any parsing or replacement.
				if ( $str_length === 1 ) {
					return 's:' . $str_length . ':"' . $str . '";';
				}

				// Attempt to parse the string as serialized data
				$pos_inner  = 0;
				$parsed_str = self::parse_serialized_values( $str, $pos_inner, $search, $replace );
				if ( $pos_inner === strlen( $str ) ) {
					// The string is serialized data, use the parsed string
					$new_str = $parsed_str;
				} else {
					// Regular string, perform replacement
					$new_str = strtr( $str, array_combine( $search, $replace ) );
				}

				return 's:' . strlen( $new_str ) . ':"' . $new_str . '";';

			case 'i':
			case 'd':
			case 'b':
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ':' ) {
					$pos--;
					return '';
				}

				$pos++;
				$end = strpos( $data, ';', $pos );
				if ( $end === false ) {
					$pos--;
					return '';
				}

				$value = substr( $data, $pos, $end - $pos );
				$pos   = $end + 1;

				return $type . ':' . $value . ';';

			case 'N':
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ';' ) {
					$pos--;
					return '';
				}

				$pos++;

				return 'N;';

			case 'a':
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ':' ) {
					$pos--;
					return '';
				}

				$pos++;
				$len_end = strpos( $data, ':', $pos );
				if ( $len_end === false ) {
					$pos--;
					return '';
				}

				$array_length = (int) substr( $data, $pos, $len_end - $pos );

				$pos = $len_end + 1;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '{' ) {
					$pos--;
					return '';
				}

				$pos++;
				$result = 'a:' . $array_length . ':{';
				for ( $i = 0; $i < $array_length * 2; $i++ ) {
					$element = self::parse_serialized_values( $data, $pos, $search, $replace );
					if ( $element === '' ) {
						$pos--;
						return '';
					}

					$result .= $element;
				}

				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '}' ) {
					$pos--;
					return '';
				}

				$pos++;
				$result .= '}';

				return $result;

			case 'O':
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ':' ) {
					$pos--;
					return '';
				}

				$pos++;
				$class_len_end = strpos( $data, ':', $pos );
				if ( $class_len_end === false ) {
					$pos--;
					return '';
				}

				$class_length = (int) substr( $data, $pos, $class_len_end - $pos );

				$pos = $class_len_end + 1;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '"' ) {
					$pos--;
					return '';
				}

				$pos++;
				$class_name = substr( $data, $pos, $class_length );

				$pos += $class_length;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '"' ) {
					$pos--;
					return '';
				}

				$pos++;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ':' ) {
					$pos--;
					return '';
				}

				$pos++;
				$prop_len_end = strpos( $data, ':', $pos );
				if ( $prop_len_end === false ) {
					$pos--;
					return '';
				}

				$prop_count = (int) substr( $data, $pos, $prop_len_end - $pos );

				$pos = $prop_len_end + 1;
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '{' ) {
					$pos--;
					return '';
				}

				$pos++;
				$result = 'O:' . strlen( $class_name ) . ':"' . $class_name . '":' . $prop_count . ':{';
				for ( $i = 0; $i < $prop_count * 2; $i++ ) {
					$element = self::parse_serialized_values( $data, $pos, $search, $replace );
					if ( $element === '' ) {
						$pos--;
						return '';
					}

					$result .= $element;
				}

				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== '}' ) {
					$pos--;
					return '';
				}

				$pos++;
				$result .= '}';

				return $result;

			case 'R':
			case 'r':
				if ( ! isset( $data[ $pos ] ) || $data[ $pos ] !== ':' ) {
					$pos--;
					return '';
				}

				$pos++;
				$end = strpos( $data, ';', $pos );
				if ( $end === false ) {
					$pos--;
					return '';
				}

				$ref = substr( $data, $pos, $end - $pos );
				$pos = $end + 1;

				return $type . ':' . $ref . ';';

			default:
				$pos--;
				return '';
		}
	}

	/**
	 * Parse MySQL server version
	 *
	 * @param  string $info Server info
	 * @return string
	 */
	public static function parse_server_version( $info ) {
		$matches = array();
		if ( preg_match( '/(\d+\.\d+\.\d+)-(\d+\.\d+\.\d+)/i', $info, $matches ) ) {
			if ( isset( $matches[2] ) ) {
				return $matches[2];
			}
		}

		if ( preg_match( '/(\d+\.\d+\.\d+)/i', $info, $matches ) ) {
			if ( isset( $matches[1] ) ) {
				return $matches[1];
			}
		}

		return '0.0.0';
	}

	/**
	 * Escape MySQL special characters
	 *
	 * @param  string $data Data to escape
	 * @return string
	 */
	public static function escape_mysql( $data ) {
		return strtr(
			$data,
			array_combine(
				array( "\x00", "\n", "\r", '\\', "'", '"', "\x1a" ),
				array( '\\0', '\\n', '\\r', '\\\\', "\\'", '\\"', '\\Z' )
			)
		);
	}

	/**
	 * Unescape MySQL special characters
	 *
	 * @param  string $data Data to unescape
	 * @return string
	 */
	public static function unescape_mysql( $data ) {
		return strtr(
			$data,
			array_combine(
				array( '\\0', '\\n', '\\r', '\\\\', "\\'", '\\"', '\\Z' ),
				array( "\x00", "\n", "\r", '\\', "'", '"', "\x1a" )
			)
		);
	}

	/**
	 * Encode base64 characters
	 *
	 * @param  string $data Data to encode
	 * @return string
	 */
	public static function base64_encode( $data ) {
		return base64_encode( $data );
	}

	/**
	 * Encode base64 characters
	 *
	 * @param  string $data Data to decode
	 * @return string
	 */
	public static function base64_decode( $data ) {
		return base64_decode( $data );
	}

	/**
	 * Validate base64 data
	 *
	 * @param  string  $data Data to validate
	 * @return boolean
	 */
	public static function base64_validate( $data ) {
		return base64_encode( base64_decode( $data ) ) === $data;
	}
}
