<?php
/** The miniOrange enables user to log in through mobile authentication as an additional layer of security over password.
 * Copyright (C) 2015  miniOrange
 *
 * 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/>
 *
 * @package        miniorange-2-factor-authentication/handler
 * @license        http://www.gnu.org/copyleft/gpl.html MIT/Expat, see LICENSE.php
 */

namespace TwoFA\Handler\Twofa;

use TwoFA\Helper\MoWpnsUtility;
use TwoFA\Helper\MocURL;
use TwoFA\Helper\MoWpnsConstants;
use DateTime;
use TwoFA\Traits\Instance;
use TwoFA\Helper\Mo2f_Filesystem;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

if ( ! class_exists( 'MO2f_Utility' ) ) {
	/**
	 * This library is miniOrange Authentication Service.
	 * Contains Request Calls to Customer service.
	 **/
	class MO2f_Utility {

		use Instance;

		/**
		 * This function get hidden phone detail
		 *
		 * @param string $phone carry the phone no.
		 * @return string
		 */
		public static function get_hidden_phone( $phone ) {
			$hidden_phone = 'xxxxxxx' . substr( $phone, strlen( $phone ) - 3 );
			return $hidden_phone;
		}
		/**
		 * Checking empty or not.
		 *
		 * @param string $value It will return the val .
		 * @return boolean
		 */
		public static function mo2f_check_empty_or_null( $value ) {
			if ( ! isset( $value ) || empty( $value ) ) {
				return true;
			}
			return false;
		}
		/**
		 * It is for curl function
		 *
		 * @return boolean
		 */
		public static function is_curl_installed() {
			if ( in_array( 'curl', get_loaded_extensions(), true ) ) {
				return 1;
			} else {
				return 0;
			}
		}
		/**
		 * Return the installed plugin name.
		 *
		 * @return void
		 */
		public static function get_all_plugins_installed() {
			$all_plugins     = get_plugins();
			$plugins         = array();
			$form            = '';
			$plugins['None'] = 'None';
			foreach ( $all_plugins as $plugin_name => $plugin_details ) {
				$plugins[ $plugin_name ] = $plugin_details['Name'];
			}
			unset( $plugins['miniorange-2-factor-authentication/miniorange-2-factor-settings.php'] );
			echo '<div class="mo2f_plugin_select">Please select the plugin<br>
			<select name="mo2f_plugin_selected" id="mo2f-plugin-selected">';
			foreach ( $plugins as $identifier => $name ) {
				echo '<option value="' . esc_attr( $identifier ) . '">' . esc_attr( $name ) . '</option>';
			}
			echo '</select></div>';
		}
		/**
		 * It will check the length of the number.
		 *
		 * @param string $token It will carry the token.
		 * @return boolean
		 */
		public static function mo2f_check_number_length( $token ) {
			if ( is_numeric( $token ) ) {
				if ( strlen( $token ) >= 4 && strlen( $token ) <= 8 ) {
					return true;
				} else {
					return false;
				}
			} else {
				return false;
			}
		}
		/**
		 * This function will help to check the email pattern is valid or not.
		 *
		 * @param string $email It will carry the email address .
		 * @return string
		 */
		public static function mo2f_get_hidden_email( $email ) {
			if ( ! isset( $email ) || trim( $email ) === '' ) {
				return '';
			}
			$emailsize    = strlen( $email );
			$partialemail = substr( $email, 0, 1 );
			$temp         = strrpos( $email, '@' );
			$endemail     = substr( $email, $temp - 1, $emailsize );
			for ( $i = 1; $i < $temp; $i++ ) {
				$partialemail = $partialemail . 'x';
			}
			$hiddenemail = $partialemail . $endemail;
			return $hiddenemail;
		}

		/**
		 * Checking the device name
		 *
		 * @param string $useragent It will carry the user agent .
		 * @return boolean
		 */
		public static function check_if_request_is_from_mobile_device( $useragent ) {
			if ( preg_match( '/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i', $useragent ) || preg_match( '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr( $useragent, 0, 4 ) ) ) {
				return true;
			} else {
				return false;
			}
		}

		/*
		Returns Random string with length provided in parameter.

		*/
		/**
		 * This function will help to decrypt the data .
		 *
		 * @param string $data It will carry the data .
		 * @param string $key It will carry the key .
		 * @return string
		 */
		public static function decrypt_data( $data, $key ) {
			$c                  = base64_decode( $data ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Not using for obfuscation
			$cipher             = 'AES-128-CBC';
			$ivlen              = openssl_cipher_iv_length( $cipher );
			$iv                 = substr( $c, 0, $ivlen );
			$hmac               = substr( $c, $ivlen, $sha2len = 32 );
			$ciphertext_raw     = substr( $c, $ivlen + $sha2len );
			$original_plaintext = openssl_decrypt( $ciphertext_raw, $cipher, $key, $options = OPENSSL_RAW_DATA, $iv );
			$calcmac            = hash_hmac( 'sha256', $ciphertext_raw, $key, $as_binary = true );
			$decrypted_text     = '';
			if ( is_string( $hmac ) && is_string( $calcmac ) ) {
				if ( hash_equals( $hmac, $calcmac ) ) {
					$decrypted_text = $original_plaintext;
				}
			}

			return $decrypted_text;
		}
		/**
		 * This function to generate the random string.
		 *
		 * @param string $length It will carry the length .
		 * @param string $keyspace It will carry thye key .
		 * @return string
		 */
		public static function random_str( $length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ) {
			$random_string   = '';
			$keyspace_length = strlen( $keyspace );
			for ( $i = 0; $i < $length; $i++ ) {
				$random_string .= $keyspace[ random_int( 0, $keyspace_length - 1 ) ];
			}

			return $random_string;
		}

		/**
		 * It is to start the session.
		 *
		 * @return void
		 */
		public static function mo2f_start_session() {
			if ( ! session_id() || '' === session_id() || ! isset( $_SESSION ) ) {
				$session_path = ini_get( 'session.save_path' );
				$file_system  = new Mo2f_Filesystem();
				if ( $file_system->mo2f_is_writable( $session_path ) && $file_system->mo2f_is_readable( $session_path ) && ! headers_sent() ) {
					if ( session_status() !== PHP_SESSION_DISABLED ) {
						session_start();
					}
				}
			}
		}

		/**
		 * This function is to check wheather it is json.
		 *
		 * @param string $mo2f_string It will the string message .
		 * @return boolean
		 */
		public static function is_json( $mo2f_string ) {
			return is_string( $mo2f_string ) && is_array( json_decode( $mo2f_string, true ) ) ? true : false;
		}

		/**
		 * It will help to encrypt the data in aes
		 *
		 * @param string $data It will pass the data of the value .
		 * @param string $key  It will pass the key of the value .
		 * @return string .
		 */
		public static function encrypt_data( $data, $key ) {
			$plaintext      = $data;
			$cipher         = 'AES-128-CBC';
			$ivlen          = openssl_cipher_iv_length( $cipher );
			$iv             = openssl_random_pseudo_bytes( $ivlen );
			$ciphertext_raw = openssl_encrypt( $plaintext, $cipher, $key, $options = OPENSSL_RAW_DATA, $iv );
			$hmac           = hash_hmac( 'sha256', $ciphertext_raw, $key, $as_binary = true );
			$ciphertext     = base64_encode( $iv . $hmac . $ciphertext_raw ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Not using for obfuscation
			return $ciphertext;
		}

		/**
		 * It will unset the session value
		 *
		 * @param object $variables Variables to unset.
		 * @param string $session_id It will carry the session id.
		 * @return void
		 */
		public static function unset_session_variables( $variables, $session_id ) {
			$enc_session_id = base64_encode( $session_id ); //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Not using for obfuscation
			if ( isset( $_SESSION[ $session_id ] ) ) {
				unset( $_SESSION[ $session_id ] );
			}
			if ( isset( $_COOKIE[ $enc_session_id ] ) ) {
				unset( $_COOKIE[ $enc_session_id ] );
			}
			if ( is_array( $variables ) ) {
				foreach ( $variables as $variable ) {
					delete_transient( $session_id . $variable );
				}
			} else {
				delete_transient( $session_id . $variables );
			}
			delete_site_option( $session_id );
		}

		/**
		 * This function is invoke to unset the temporaray user detail
		 *
		 * @param string $variables .
		 * @param string $session_id It will carry the session id .
		 * @param string $command It will carry the command message .
		 * @return void
		 */
		public static function unset_temp_user_details_in_table( $variables, $session_id, $command = '' ) {
			global $mo2fdb_queries;
			$key             = get_site_option( 'mo2f_encryption_key' );
			$session_id      = self::decrypt_data( $session_id, $key );
			$session_id_hash = md5( $session_id );
			if ( 'destroy' === $command ) {
				$mo2fdb_queries->delete_user_login_sessions( $session_id_hash );
			} else {
				$mo2fdb_queries->save_user_login_details( $session_id_hash, array( $variables => '' ) );
			}
		}

		/**
		 * Get plugin name by identifier
		 *
		 * @param string $plugin_identitifier .
		 * @return string .
		 */
		public static function get_plugin_name_by_identifier( $plugin_identitifier ) {
			if ( 'None' === $plugin_identitifier ) {
				return 'None';
			}
			$all_plugins = get_plugins();

			if ( ! isset( $all_plugins[ $plugin_identitifier ] ) ) {
				return 'No Plugin selected';
			}
			$plugin_details = $all_plugins[ $plugin_identitifier ];
			if ( is_array( $plugin_details ) && isset( $plugin_details['Name'] ) ) {
				return $plugin_details['Name'];
			}
			return 'No Plugin selected';
		}
		/**
		 * It will return the index is exist or not
		 *
		 * @param string $variable It will store the variable data .
		 * @param string $index It will carry the index value .
		 * @return boolean .
		 */
		public static function get_index_value( $variable, $index ) {
			switch ( $variable ) {
				case 'GLOBALS':
					return isset( $GLOBALS[ $index ] ) ? $GLOBALS[ $index ] : false;
				default:
					return false;
			}
		}
		/**
		 * This function will show the warning message over email
		 *
		 * @param string $codes_remaining It will show the code remaining.
		 * @return string .
		 */
		public static function get_codes_warning_email_content( $codes_remaining ) {
			global $mo2f_image_path;
			$message = '<table cellpadding="25" style="margin:0px auto">
        <tbody>
        <tr>
        <td>
        <table cellpadding="24" width="584px" style="margin:0 auto;max-width:584px;background-color:#f6f4f4;border:1px solid #a8adad">
        <tbody>
        <tr>
        <td><img src="' . esc_url( $mo2f_image_path ) . 'includes/images/xecurify-logo.png" alt="Xecurify" style="color:#5fb336;text-decoration:none;display:block;width:auto;height:auto;max-height:35px" class="CToWUd"></td>
        </tr>
        </tbody>
        </table>
        <table cellpadding="24" style="background:#fff;border:1px solid #a8adad;width:584px;border-top:none;color:#4d4b48;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:18px">
        <tbody>
        <tr>
        <td>
        <p style="margin-top:0;margin-bottom:20px">Dear Customer,</p>
        <p style="margin-top:0;margin-bottom:10px">You have ' . $codes_remaining . ' backup codes remaining. Kindly reconfigure your two-factor to avoid being locked out.</b></p>
        <p style="margin-top:0;margin-bottom:15px">Thank you,<br>miniOrange Team</p>
        <p style="margin-top:0;margin-bottom:0px;font-size:11px">Disclaimer: This email and any files transmitted with it are confidential and intended solely for the use of the individual or entity to whom they are addressed.</p>
        </div></div></td>
        </tr>
        </tbody>
        </table>
        </td>
        </tr>
        </tbody>
        </table>';
			return $message;
		}

		/**
		 * Sanitizes SVG content by removing potentially dangerous elements and attributes.
		 *
		 * @param string $svg_string Raw SVG content.
		 * @return string Sanitized SVG content or empty string if invalid
		 */
		public static function mo2f_sanitize_svg( $svg_string ) {
			$svg_string = is_string( $svg_string ) ? $svg_string : '';
			if ( '' === $svg_string ) {
				return '';
			}
			// Strip NUL bytes and any DOCTYPE to avoid XXE and entity attacks.
			$svg_string = preg_replace( '/\x00+/', '', $svg_string );
			$svg_string = preg_replace( '/<!DOCTYPE[^>]*>/i', '', $svg_string );
			// Prefer DOM-based sanitization when DOM extension is available.
			if ( class_exists( '\\DOMDocument' ) ) {
				$dom      = new \DOMDocument();
				$previous = libxml_use_internal_errors( true );
				// Disable network access, ignore errors, compact tree, remove blanks.
				$loaded = $dom->loadXML( $svg_string, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_COMPACT | LIBXML_NOBLANKS );
				libxml_clear_errors();
				libxml_use_internal_errors( $previous );
				// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- documentElement is a built-in PHP DOMDocument property.
				if ( ! $loaded || ! $dom->documentElement || 'svg' !== strtolower( $dom->documentElement->tagName ) ) {
					return '';
				}
				$xpath = new \DOMXPath( $dom );
				// Remove dangerous elements in a single XPath query.
				$danger_query = '//*[local-name()="script" or local-name()="foreignObject" or local-name()="iframe" or local-name()="object" or local-name()="embed" or local-name()="audio" or local-name()="video" or local-name()="canvas" or local-name()="animate" or local-name()="animateTransform" or local-name()="set" or local-name()="style"]';
				$nodes        = $xpath->query( $danger_query );
				if ( $nodes && $nodes->length ) {
					// Collect first to avoid live list issues.
					$to_remove = array();
					foreach ( $nodes as $n ) {
						$to_remove[] = $n;
					}
					foreach ( $to_remove as $n ) {
						// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- parentNode is a built-in PHP DOMNode property.
						if ( $n->parentNode ) {
							// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- parentNode is a built-in PHP DOMNode property.
							$n->parentNode->removeChild( $n );
						}
					}
				}
				// Clean attributes from all remaining elements.
				$elements = $xpath->query( '//*' );
				if ( $elements && $elements->length ) {
					foreach ( $elements as $el ) {
						if ( ! $el->hasAttributes() ) {
							continue;
						}
						$attributes_to_remove = array();
						foreach ( $el->attributes as $attr ) {
							$attr_name = strtolower( $attr->name );
							// Remove event handlers and risky linking/style attributes.
							if ( 0 === strpos( $attr_name, 'on' ) || 'style' === $attr_name || 'href' === $attr_name || 'xlink:href' === $attr_name || 'src' === $attr_name || 'filter' === $attr_name || 'mask' === $attr_name || 'clip-path' === $attr_name ) {
								$attributes_to_remove[] = $attr->name;
								continue;
							}
							$attr_value = (string) $attr->value;
							// Strip dangerous protocols without lowercasing whole string.
							if ( false !== stripos( $attr_value, 'javascript:' ) || 0 === stripos( $attr_value, 'data:' ) || false !== stripos( $attr_value, 'vbscript:' ) || 0 === stripos( $attr_value, 'file:' ) ) {
								$attributes_to_remove[] = $attr->name;
							}
						}
						foreach ( $attributes_to_remove as $attr_name ) {
							$el->removeAttribute( $attr_name );
						}
					}
				}
				// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- documentElement is a built-in PHP DOMDocument property.
				return trim( $dom->saveXML( $dom->documentElement ) );
			}
			// Fallback regex-based sanitization.
			$patterns   = array(
				'/<script[\s\S]*?<\/script>/i',
				'/<foreignObject[\s\S]*?<\/foreignObject>/i',
				'/<iframe[\s\S]*?<\/iframe>/i',
				'/<object[\s\S]*?<\/object>/i',
				'/<embed[\s\S]*?<\/embed>/i',
				'/<audio[\s\S]*?<\/audio>/i',
				'/<video[\s\S]*?<\/video>/i',
				'/<canvas[\s\S]*?<\/canvas>/i',
				'/<style[\s\S]*?<\/style>/i',
			);
			$svg_string = preg_replace( $patterns, '', $svg_string );
			// Remove event handler attributes and risky links/styles.
			$svg_string = preg_replace( '/\s+on[a-z-]+\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $svg_string );
			$svg_string = preg_replace( '/\s+(href|xlink:href|src)\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $svg_string );
			$svg_string = preg_replace( '/\s+style\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $svg_string );
			$svg_string = preg_replace( '/\s+(filter|mask|clip-path)\s*=\s*("[^"]*"|\'[^\']*\'|[^\s>]+)/i', '', $svg_string );
			return trim( $svg_string );
		}

		/**
		 * It will invoke to send the back up code over email
		 *
		 * @param string $codes It will carry the back up code .
		 * @param string $mo2f_user_email It will carry the user email .
		 * @return string .
		 */
		public static function mo2f_email_backup_codes( $codes, $mo2f_user_email ) {
			$subject = MoWpnsUtility::get_mo2f_db_option( 'mo2f_2fa_backup_code_email_subject', 'site_option' );
			$headers = array( 'Content-Type: text/html; charset=UTF-8' );
			$message = MoWpnsUtility::get_mo2f_db_option( 'mo2f_backup_code_email_template', 'site_option' );
			$message = str_replace( '##code1##', isset( $codes[0] ) ? $codes[0] : '', $message );
			$message = str_replace( '##code2##', isset( $codes[1] ) ? $codes[1] : '', $message );
			$message = str_replace( '##code3##', isset( $codes[2] ) ? $codes[2] : '', $message );
			$message = str_replace( '##code4##', isset( $codes[3] ) ? $codes[3] : '', $message );
			$message = str_replace( '##code5##', isset( $codes[4] ) ? $codes[4] : '', $message );
			$result  = wp_mail( $mo2f_user_email, $subject, $message, $headers );
			return $result;
		}
		/**
		 * This function will invoke at the time of download backup code
		 *
		 * @param string $id It will carry the user id.
		 * @param mixed  $codes It will carry the code.
		 * @return void
		 */
		public static function mo2f_download_backup_codes( $id, $codes ) {
			$codes = is_array( $codes ) ? $codes : explode( ' ', $codes );
			update_user_meta( $id, 'mo_backup_code_downloaded', 1 );
			header( 'Content-Disposition: attachment; filename=miniOrange2-factor-BackupCodes.txt' );
			echo 'Two Factor Backup Codes:' . PHP_EOL . PHP_EOL;
			echo 'These are the codes that can be used in case you lose your phone or cannot access your email. Please reconfigure your authentication method after login.' . PHP_EOL . 'Please use this carefully as each code can only be used once. Please do not share these codes with anyone..' . PHP_EOL . PHP_EOL;
			$size_of = count( $codes );
			for ( $x = 0; $x < $size_of; $x++ ) {
				$str1 = $codes[ $x ];
				echo( intval( $x + 1 ) . '. ' . esc_html( $str1 ) . ' ' );
			}

			exit;
		}
		/**
		 * This function is called when debug log is on
		 *
		 * @param string $text It will carry the text data .
		 * @return void
		 */
		public static function mo2f_debug_file( $text ) {
			if ( (int) MoWpnsUtility::get_mo2f_db_option( 'mo2f_enable_debug_log', 'site_option' ) ) {
				$plugin_root_path = dirname( dirname( plugin_dir_path( __FILE__ ) ) );
				$debug_log_path   = $plugin_root_path . DIRECTORY_SEPARATOR . 'logs';
				if ( ! is_dir( $debug_log_path ) ) {
					$dir_created = wp_mkdir_p( $debug_log_path );
					if ( ! $dir_created ) {
						return;
					}
				}

				$filename   = 'miniorange_2FA_plugin_debug_log.php';
				$file_path  = $debug_log_path . DIRECTORY_SEPARATOR . $filename;
				$php_header = "<?php if ( ! defined( 'ABSPATH' ) ) { exit; } ?>";

				if ( ! function_exists( 'WP_Filesystem' ) ) {
					require_once ABSPATH . 'wp-admin/includes/file.php';
				}

				global $wp_filesystem;

				if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
					WP_Filesystem();
				}

				$use_wp_filesystem = ( $wp_filesystem instanceof WP_Filesystem_Base );

				$existing_log = '';

				if ( $use_wp_filesystem ) {
					if ( $wp_filesystem->exists( $file_path ) ) {
						$existing_log = $wp_filesystem->get_contents( $file_path );
						if ( false === $existing_log ) {
							$existing_log = '';
						}
					} else {
						$existing_log = $php_header . PHP_EOL;
					}
				} elseif ( file_exists( $file_path ) ) {
					// Fallback to direct PHP file operations.
					$existing_log = @file_get_contents( $file_path );
					if ( false === $existing_log ) {
						$existing_log = '';
					}
				} else {
					$existing_log = $php_header . PHP_EOL;
				}

				if ( 0 !== strpos( $existing_log, $php_header ) ) {
					$existing_log = $php_header . PHP_EOL . ltrim( $existing_log, "\r\n" );
				}

				$data = '[' . gmdate( 'Y/m/d' ) . ' ' . time() . ']:' . $text . "\n";

				if ( $use_wp_filesystem ) {
					$wp_filesystem->put_contents(
						$file_path,
						$existing_log . $data,
						defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : false
					);
				} else {
					// Fallback to direct PHP file operations.
					@file_put_contents( $file_path, $existing_log . $data, LOCK_EX );
					if ( defined( 'FS_CHMOD_FILE' ) && isset( $wp_filesystem ) && method_exists( $wp_filesystem, 'chmod' ) ) {
						$wp_filesystem->chmod( $file_path, FS_CHMOD_FILE );
					}
				}
			}
		}

		/**
		 * Show Invalid User Credentials error on ajax
		 *
		 * @param string $message Message.
		 * @return array
		 */
		public static function mo2f_show_error_on_login( $message ) {
			$data = array( 'notice' => '<div style="border-left:3px solid #dc3232;">&nbsp; ' . $message . '' );
			return $data;
		}
	}
}
