<?php
if (!defined('ABSPATH')) {
	exit;//Exit if accessed directly
}

class AIOWPSecurity_Installer {

	private static $db_tasks = array(
		'2.0.2' => array(
			'clean_audit_log_stacktraces',
		),
		'2.0.7' => array(
			'add_ip_lookup_result_field_to_ip_lockout_table',
		)
	);

	/**
	 * Run installer function.
	 *
	 * @param boolean $networkwide
	 * @return void
	 */
	public static function run_installer($networkwide = '') {
		global $wpdb;
		if (function_exists('is_multisite') && is_multisite() && $networkwide) {
			// check if it is a network activation - if so, run the activation function for each blog id
				$blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
				foreach ($blogids as $blog_id) {
					switch_to_blog($blog_id);
					AIOWPSecurity_Installer::create_db_tables();
					AIOWPSecurity_Installer::migrate_db_tables();
					AIOWPSecurity_Installer::check_tasks();
					AIOWPSecurity_Configure_Settings::add_option_values();
					restore_current_blog();
				}
				AIOWPSecurity_Installer::create_db_backup_dir(); //Create a backup dir in the WP uploads directory
		} else {
			AIOWPSecurity_Installer::create_db_tables();
			AIOWPSecurity_Installer::migrate_db_tables();
			AIOWPSecurity_Installer::check_tasks();
			AIOWPSecurity_Configure_Settings::add_option_values();
			AIOWPSecurity_Installer::create_db_backup_dir(); //Create a backup dir in the WP uploads directory
		}
	}

	/**
	 * See if any database tasks need to be run, and perform them if so.
	 *
	 * @return void
	 */
	public static function check_tasks() {
		$our_version = AIO_WP_SECURITY_DB_VERSION;
		$db_version = get_option('aiowpsec_db_version', '1.0');
		if (version_compare($our_version, $db_version, '>')) {
			foreach (self::$db_tasks as $version => $updates) {
				if (version_compare($version, $db_version, '>')) {
					foreach ($updates as $update) {
						call_user_func(array(__CLASS__, $update));
					}
				}
			}
		}
	}

	public static function create_db_tables() {
		global $wpdb;

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

		if (function_exists('is_multisite') && is_multisite()) {
			/*
			 * FIX for multisite table creation case:
			 * Although each table name is defined in a constant inside the wp-security-core.php,
			 * we need to do this step for multisite case because we need to refresh the $wpdb->prefix value
			 * otherwise it will contain the original blog id and not the current id we need.
			 *
			 */
			$lockout_tbl_name = $wpdb->prefix.'aiowps_login_lockdown';
			$aiowps_global_meta_tbl_name = $wpdb->prefix.'aiowps_global_meta';
			$aiowps_event_tbl_name = $wpdb->prefix.'aiowps_events';
			$perm_block_tbl_name = $wpdb->prefix.'aiowps_permanent_block';
			
		} else {
			$lockout_tbl_name = AIOWPSEC_TBL_LOGIN_LOCKOUT;
			$aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
			$aiowps_event_tbl_name = AIOWPSEC_TBL_EVENTS;
			$perm_block_tbl_name = AIOWPSEC_TBL_PERM_BLOCK;
		}

		$message_store_log_tbl_name = AIOWPSEC_TBL_MESSAGE_STORE;
		$audit_log_tbl_name = AIOWPSEC_TBL_AUDIT_LOG;
		$debug_log_tbl_name = AIOWPSEC_TBL_DEBUG_LOG;
		$logged_in_users_tbl_name = AIOWSPEC_TBL_LOGGED_IN_USERS;

		$charset_collate = '';
		if (!empty($wpdb->charset)) {
			$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
		} else {
			$charset_collate = "DEFAULT CHARSET=utf8";
		}
		if (!empty($wpdb->collate)) {
			$charset_collate .= " COLLATE $wpdb->collate";
		}

		$ld_tbl_sql = "CREATE TABLE " . $lockout_tbl_name . " (
		id bigint(20) NOT NULL AUTO_INCREMENT,
		user_id bigint(20) NOT NULL,
		user_login VARCHAR(150) NOT NULL,
		lockdown_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
		release_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
		failed_login_ip varchar(100) NOT NULL DEFAULT '',
		lock_reason varchar(128) NOT NULL DEFAULT '',
		unlock_key varchar(128) NOT NULL DEFAULT '',
		is_lockout_email_sent tinyint(1) NOT NULL DEFAULT '1',
		backtrace_log text NOT NULL DEFAULT '',
		PRIMARY KEY  (id),
		  KEY failed_login_ip (failed_login_ip),
		  KEY is_lockout_email_sent (is_lockout_email_sent),
		  KEY unlock_key (unlock_key)
		)" . $charset_collate . ";";
		dbDelta($ld_tbl_sql);

		$gm_tbl_sql = "CREATE TABLE " . $aiowps_global_meta_tbl_name . " (
		meta_id bigint(20) NOT NULL auto_increment,
		date_time datetime NOT NULL default '1000-10-10 10:00:00',
		meta_key1 varchar(255) NOT NULL,
		meta_key2 varchar(255) NOT NULL,
		meta_key3 varchar(255) NOT NULL,
		meta_key4 varchar(255) NOT NULL,
		meta_key5 varchar(255) NOT NULL,
		meta_value1 varchar(255) NOT NULL,
		meta_value2 text NOT NULL,
		meta_value3 text NOT NULL,
		meta_value4 longtext NOT NULL,
		meta_value5 longtext NOT NULL,
		PRIMARY KEY  (meta_id)
		)" . $charset_collate . ";";
		dbDelta($gm_tbl_sql);

		$evt_tbl_sql = "CREATE TABLE " . $aiowps_event_tbl_name . " (
		id bigint(20) NOT NULL AUTO_INCREMENT,
		event_type VARCHAR(150) NOT NULL DEFAULT '',
		username VARCHAR(150),
		user_id bigint(20),
		event_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
		ip_or_host varchar(100),
		referer_info varchar(255),
		url varchar(255),
		country_code varchar(50),
		event_data longtext,
		PRIMARY KEY  (id)
		)" . $charset_collate . ";";
		dbDelta($evt_tbl_sql);

		$pb_tbl_sql = "CREATE TABLE " . $perm_block_tbl_name . " (
		id bigint(20) NOT NULL AUTO_INCREMENT,
		blocked_ip varchar(100) NOT NULL DEFAULT '',
		block_reason varchar(128) NOT NULL DEFAULT '',
		country_origin varchar(50) NOT NULL DEFAULT '',
		blocked_date datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
		unblock tinyint(1) NOT NULL DEFAULT '0',
		PRIMARY KEY  (id),
		KEY blocked_ip (blocked_ip)
		)" . $charset_collate . ";";
		dbDelta($pb_tbl_sql);

		$audit_log_tbl_sql = "CREATE TABLE " . $audit_log_tbl_name . " (
			id bigint(20) NOT NULL AUTO_INCREMENT,
			network_id bigint(20) NOT NULL DEFAULT '0',
			site_id bigint(20) NOT NULL DEFAULT '0',
			username varchar(60) NOT NULL DEFAULT '',
			ip VARCHAR(45) NOT NULL DEFAULT '',
			level varchar(25) NOT NULL DEFAULT '',
			event_type varchar(25) NOT NULL DEFAULT '',
			details text NOT NULL DEFAULT '',
			stacktrace text NOT NULL DEFAULT '',
			created INTEGER UNSIGNED,
			PRIMARY KEY  (id),
			INDEX username (username),
			INDEX ip (ip),
			INDEX level (level),
			INDEX event_type (event_type)
			)" . $charset_collate . ";";
		dbDelta($audit_log_tbl_sql);

		$debug_log_tbl_sql = "CREATE TABLE " . $debug_log_tbl_name . " (
			id bigint(20) NOT NULL AUTO_INCREMENT,
			created datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
			level varchar(25) NOT NULL DEFAULT '',
			network_id bigint(20) NOT NULL DEFAULT '0',
			site_id bigint(20) NOT NULL DEFAULT '0',
			message text NOT NULL DEFAULT '',
			type varchar(25) NOT NULL DEFAULT '',
			PRIMARY KEY  (id)
			)" . $charset_collate . ";";
		dbDelta($debug_log_tbl_sql);

		$liu_tbl_sql = "CREATE TABLE " . $logged_in_users_tbl_name . " (
			id bigint(20) NOT NULL AUTO_INCREMENT,
			user_id bigint(20) NOT NULL,
			username varchar(60) NOT NULL DEFAULT '',
			ip_address varchar(45) NOT NULL DEFAULT '',
			site_id bigint(20) NOT NULL,
			created integer UNSIGNED,
			expires integer UNSIGNED,
			PRIMARY KEY (id),
			UNIQUE KEY unique_user_id (user_id),
			INDEX created (created),
			INDEX expires (expires),
			INDEX user_id (user_id),
			INDEX site_id (site_id)
			) " . $charset_collate . ";";
		dbDelta($liu_tbl_sql);

		$message_store_log_tbl_sql = "CREATE TABLE " . $message_store_log_tbl_name . " (
			id bigint(20) NOT NULL AUTO_INCREMENT,
			message_key text NOT NULL DEFAULT '',
			message_value text NOT NULL DEFAULT '',
			created INTEGER UNSIGNED,
			PRIMARY KEY  (id)
			)" . $charset_collate . ";";
		dbDelta($message_store_log_tbl_sql);
	}

	/**
	 * This function will handle any database table migrations
	 *
	 * @return void
	 */
	public static function migrate_db_tables() {
		global $wpdb;

		if (function_exists('is_multisite') && is_multisite()) {
			/*
			 * FIX for multisite table creation case:
			 * Although each table name is defined in a constant inside the wp-security-core.php,
			 * we need to do this step for multisite case because we need to refresh the $wpdb->prefix value
			 * otherwise it will contain the original blog id and not the current id we need.
			 *
			 */
			$failed_login_tbl_name = $wpdb->prefix.'aiowps_failed_logins';
			$login_activity_tbl_name = $wpdb->prefix.'aiowps_login_activity';
			
		} else {
			$failed_login_tbl_name = AIOWPSEC_TBL_FAILED_LOGINS;
			$login_activity_tbl_name = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
		}

		$audit_log_tbl_name = AIOWPSEC_TBL_AUDIT_LOG;
		$network_id = get_current_network_id();
		$site_id = get_current_blog_id();
		
		$query = $wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($failed_login_tbl_name));
		$table_exists = $wpdb->get_var($query);
		if ($table_exists) {
			$import_details = array(
				'failed_login' => array(
					'imported' => true,
				)
			);
			$import_details = json_encode($import_details, true);
			$table_migration_details = array(
				'table_migration' => array(
					'success' => true,
					'from_table' => $failed_login_tbl_name,
					'to_table' => $audit_log_tbl_name
				)
			);

			if (false === $wpdb->query($wpdb->prepare("INSERT INTO $audit_log_tbl_name (network_id, site_id, username, ip, level, event_type, details, stacktrace, created) SELECT %d AS network_id, %d AS site_id, fl.user_login AS username, fl.login_attempt_ip AS ip, 'warning' AS level, 'Failed login' AS event_type, %s AS details, '' AS stacktrace, UNIX_TIMESTAMP(fl.failed_login_date) AS created FROM $failed_login_tbl_name fl", $network_id, $site_id, $import_details))) {
				$table_migration_details['table_migration']['success'] = false;
				do_action('aiowps_record_event', 'table_migration', $table_migration_details, 'error');
			} else {
				do_action('aiowps_record_event', 'table_migration', $table_migration_details, 'info');
				$wpdb->query("DROP TABLE IF EXISTS `$failed_login_tbl_name`");
			}
		}

		$query = $wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($login_activity_tbl_name));
		$table_exists = $wpdb->get_var($query);
		if ($table_exists) {
			$wpdb->query("DROP TABLE IF EXISTS `$login_activity_tbl_name`");
		}
	}

	/**
	 * This function will run SQL to clean sensitive information from the audit log table stacktrace
	 *
	 * @return void
	 */
	public static function clean_audit_log_stacktraces() {
		global $wpdb;
		$wpdb->query("UPDATE ".AIOWPSEC_TBL_AUDIT_LOG." SET stacktrace = '' WHERE event_type = 'failed_login' OR event_type = 'successful_login' OR event_type = 'user_registration'");
	}

	/**
	 * This function will add the ip result lookup column to the login lockdown table
	 *
	 * @return void
	 */
	public static function add_ip_lookup_result_field_to_ip_lockout_table() {
		global $wpdb;

		$column_name = 'ip_lookup_result';
		if (is_multisite()) {
			$blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
			foreach ($blogids as $blog_id) {
				switch_to_blog($blog_id);
				$login_lockdown_table = $wpdb->prefix.'aiowps_login_lockdown';
				self::add_column_to_table($column_name, $login_lockdown_table);
				restore_current_blog();
			}
		} else {
			$login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKOUT;
			self::add_column_to_table($column_name, $login_lockdown_table);
		}

	}

	/**
	 * This function adds a column to a specified table
	 *
	 * @param string $column_name - This is the column being added to the table
	 * @param string $table_name  - This is the table name we are adding the column to
	 *
	 * @return void
	 */
	public static function add_column_to_table($column_name, $table_name) {
		global $wpdb, $aio_wp_security;

		// Check if the column already exists
		$column_exists = $wpdb->get_var("SHOW COLUMNS FROM $table_name LIKE '$column_name'");

		if (!$column_exists) {
			// Add the new column to the table
			$result = $wpdb->query("ALTER TABLE `{$table_name}` ADD COLUMN `$column_name` LONGTEXT DEFAULT NULL");
			if (false === $result) {
				$error_message = empty($wpdb->last_error) ? "Error creating column $column_name in $table_name" : $wpdb->last_error;
				$aio_wp_security->debug_logger->log_debug($error_message, 4);
			}
		}
	}


	public static function create_db_backup_dir() {
		global $aio_wp_security;
		//Create our folder in the "wp-content" directory
		$aiowps_dir = WP_CONTENT_DIR . '/' . AIO_WP_SECURITY_BACKUPS_DIR_NAME;
		if (!is_dir($aiowps_dir) && is_writable(WP_CONTENT_DIR)) {
			mkdir($aiowps_dir, 0755, true);
			//Let's also create an empty index.html file in this folder
			$index_file = $aiowps_dir . '/index.html';
			$handle = fopen($index_file, 'w'); //or die('Cannot open file:  '.$index_file);
			fclose($handle);
		}
		$server_type = AIOWPSecurity_Utility::get_server_type();
		//Only create .htaccess if server is the right type
		if ('apache' == $server_type || 'litespeed' == $server_type) {
			$file = $aiowps_dir . '/.htaccess';
			if (!file_exists($file)) {
				//Create an .htacces file
				//Write some rules which will only allow people originating from wp admin page to download the DB backup
				$rules = '';
				$rules .= 'order deny,allow' . PHP_EOL;
				$rules .= 'deny from all' . PHP_EOL;
				$write_result = file_put_contents($file, $rules);
				if (false === $write_result) {
					$aio_wp_security->debug_logger->log_debug("Creation of .htaccess file in " . AIO_WP_SECURITY_BACKUPS_DIR_NAME . " directory failed!", 4);
				}
			}
		}
	}

	/**
	 * Restores original config settings and .htaccess file rules from before the last deactivation.
	 *
	 * @global AIO_WP_Security $aio_wp_security
	 *
	 * @return Boolean - whether or not the restoration succeeded
	 */
	public static function reactivation_tasks() {
		global $aio_wp_security;

		$temp_configs = get_option('aiowps_temp_configs');

		if (false !== $temp_configs) {
			// Case where previously installed plugin is reactivated
			// Let's copy the original configs back to the options table
			$updated = update_option('aio_wp_security_configs', $temp_configs);

			if (!$updated) {
				if (get_option('aio_wp_security_configs') === $temp_configs) {
					delete_option('aiowps_temp_configs');
					return true;
				}

				$aio_wp_security->debug_logger->log_debug('AIOWPSecurity_Installer::reactivation_tasks() - Restoration of original config settings failed.', 4);
				return false;
			}

			// Load the restored config settings to the configs object
			$aio_wp_security->configs->load_config();

			if (is_main_site() && is_super_admin()) {
				// Now let's write any rules to the .htaccess file if necessary
				$result = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
				AIOWPSecurity_Configure_Settings::reapply_firewall_configs();

				if (!$result) {
					$aio_wp_security->debug_logger->log_debug('AIOWPSecurity_Installer::reactivation_tasks() - Could not write to the .htaccess file. Please check the file permissions.', 4);
					return false;
				}
			}

			delete_option('aiowps_temp_configs');

			return true;
		} else {
			$aio_wp_security->debug_logger->log_debug('AIOWPSecurity_Installer::reactivation_tasks() - Original config settings not found.', 4);
			return false;
		}
	}

	/**
	 * Setup AIOS cron tasks.
	 * Handles both single and multi-site (NW activation) cases.
	 *
	 * @global type $wpdb
	 * @param Boolean $networkwide Whether set cronjob networkwide or normal site.
	 * @return Void
	 */
	public static function set_cron_tasks_upon_activation($networkwide = false) {
		require_once(__DIR__.'/wp-security-cronjob-handler.php');
		// It is required because we are going to schedule a 15-minute cron event upon activation.
		add_filter('cron_schedules', array('AIOWPSecurity_Cronjob_Handler', 'cron_schedules'));
		if (is_multisite() && $networkwide) {
			global $wpdb;
			// check if it is a network activation
			$blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
			foreach ($blogids as $blog_id) {
				switch_to_blog($blog_id);
				AIOWPSecurity_Installer::schedule_cron_events();
				do_action('aiowps_activation_complete');
				restore_current_blog();
			}
		} else {
			AIOWPSecurity_Installer::schedule_cron_events();
			do_action('aiowps_activation_complete');
		}
	}

	/**
	 * Helper function for scheduling AIOS cron events.
	 *
	 * @return Void
	 */
	public static function schedule_cron_events() {
		if (!wp_next_scheduled('aios_15_minutes_cron_event')) {
			wp_schedule_event(time(), 'aios-every-15-minutes', 'aios_15_minutes_cron_event'); //schedule a 15 minutes cron event
		}
		if (!wp_next_scheduled('aiowps_hourly_cron_event')) {
			wp_schedule_event(time(), 'hourly', 'aiowps_hourly_cron_event'); //schedule an hourly cron event
		}
		if (!wp_next_scheduled('aiowps_daily_cron_event')) {
			wp_schedule_event(time(), 'daily', 'aiowps_daily_cron_event'); //schedule an daily cron event
		}
		if (!wp_next_scheduled('aiowps_weekly_cron_event')) {
			wp_schedule_event(time(), 'weekly', 'aiowps_weekly_cron_event'); //schedule an daily cron event
		}
	}
}
