package SB::Update;

use strict;
use warnings;
use SB::Defaults qw($CONFIG $SB_HTDOCS $SB_CONFIG $SB_PREFIX);
use SB::Logging qw($Logger INFO ERROR FATAL WARNING DEBUG VERYVERBOSE);
use SB::Utils qw(listenHostForPort restorecon getUuid run
				 writePlainConfig parsePlainConfig);
use SB::Messages;
use POSIX qw(strftime);
use Digest::MD5 qw(md5_hex);
require SB::OS;
require File::Basename;
require File::Spec;
require File::Copy;

use subs qw();
use vars qw();

BEGIN {
	use Exporter;
	our (@ISA, $VERSION, @EXPORT, @EXPORT_OK);
	$VERSION   =  4.00;
	@ISA = qw(Exporter);
	@EXPORT    = qw();
	@EXPORT_OK = qw(updateModules updateLocales updateTemplates);
}

sub _processModuleInstallCode($$) {
	my $code = shift;
	my $module = shift;
	my %codes = (
		0  => [INFO, "The $module module has been successfully updated"],
		5  => [DEBUG, "The $module module has incompatible version, will be upgraded later"],
		4  => [DEBUG, "This module ($module) is already installed"],
		-1 => [WARNING, "Installation of $module module has failed ($code)" ]
	);
	if (!exists($codes{$code})) {
		$code = -1;
	}
	$Logger->Log($codes{$code}->[0], $codes{$code}->[1]);
}

sub updateModule($$) {
	my $module = shift;
	my $mode = shift;
	my $phpcli_path = $CONFIG->{"internal"}->{"phpcli_path"};;
	my (undef, $status) = ($mode == 1)
		? run("$phpcli_path -q $SB_PREFIX/utils/modules/install.php $module")
		: run("$phpcli_path -q $SB_PREFIX/utils/modules/uninstall.php $module");
	
	return int($status);
}

sub updateModules($) {
	my $status;
	my $modules = shift;
	my $phpcli_path = $CONFIG->{"internal"}->{"phpcli_path"};
	
	my ($mod_inst, undef) = run("$phpcli_path -q $SB_PREFIX/utils/modules/check.php db 2>/dev/null");
	if ($mod_inst =~ /[e|E]rror/) {
		$Logger->Log(ERROR, $mod_inst);
		return 1;
	}

	foreach my $module (split(' ', $mod_inst)) {
		next unless ((not @$modules) || (scalar grep $_ eq $module, @$modules));
		my $code = updateModule($module, 1) if $module;
		_processModuleInstallCode($code, $module);
	}

	($mod_inst, undef) = run("$phpcli_path -q $SB_PREFIX/utils/modules/check.php source 2>/dev/null");
	if ($mod_inst =~ /[e|E]rror/) {
		$Logger->Log(ERROR, $mod_inst);
		return 1;
	}
	foreach my $module (split(' ', $mod_inst)) {
		next unless ((not @$modules) || (scalar grep $_ eq $module, @$modules));
		my $code = updateModule($module, 0) if $module;
		_processModuleInstallCode($code, $module);
	}

	return 0;
}

sub updateLocales () {
	my $phpcli_path = $CONFIG->{"internal"}->{"phpcli_path"};
	my (undef, $code) = run("$phpcli_path -q $SB_PREFIX/utils/update_locales.php all");
	if ($code == 0) {
		$Logger->Log(INFO, "Locales have been successfully updated");
	}
	else {
		$Logger->Log(ERROR, "Errors during locales update");
		return 1;
	}
	return 0;
}

sub updateTemplates() {
	my $phpcli_path = $CONFIG->{"internal"}->{"phpcli_path"};;
	my (undef, $code) = run("$phpcli_path -q $SB_PREFIX/utils/update_templates.php all");
	if ($code == 0) {
		$Logger->Log(INFO, "Templates have been successfully updated");
	}
	else {
		$Logger->Log(ERROR, "Errors during templates update");
		return 1;
	}
	return 0;
}

sub makeVHost {
	my $filePath 	= $SB_PREFIX . "/utils/sitebuilder.conf";

	my $sb_hostname = $CONFIG->{"internal"}->{"sb_hostname"};
	my $httpd_port	= $CONFIG->{"internal"}->{"httpd_port"};
	my $host_ip 	= $CONFIG->{"internal"}->{'sb_host_ip'};
	my $phpcgi_path = $CONFIG->{"internal"}->{"phpcgi_path"};
	my $enable_ssl	= $CONFIG->{"internal"}->{"enable_ssl"};
	my $errorLogDir = SB::OS::getApacheErrorLogDir();
	my $ssl_template = "";
	my $APACHE_CTL_CMD;

	my $basename = File::Basename::basename($phpcgi_path);
	my $dirname  = File::Basename::dirname($phpcgi_path);
	my $apacheError = 0;
	my $code = 0;
	my $output;

	my $fileContent;
	
	$Logger->Log(DEBUG, "Plesk detected") if (SB::OS::hasPleskInstalled());
	$Logger->Log(DEBUG, "CPanel detected") if (SB::OS::hasCPanelInstalled());

	{
		unless (open(F, "< $filePath")) {
			$Logger->Log(ERROR, "Cannot open file $filePath: $!");
			return 1;
		}
		binmode(F);
		local $/;
		$fileContent = <F>;
		close(F);
	}

	if ($enable_ssl) {
		$Logger->Log(DEBUG, "SSL enabled");
		$httpd_port = 443 if $httpd_port == 80;
		$ssl_template =<<"EOC";
	SSLEngine on
	SSLVerifyClient none
	SSLCertificateFile    $SB_PREFIX/etc/server.crt
	SSLCertificateKeyFile $SB_PREFIX/etc/server.pem
EOC
	}

	$fileContent =~ s|__ssl__|$ssl_template|mg;
	$fileContent =~ s|__hostip__|$host_ip|g;
	$fileContent =~ s|__sitebuilder_path__|$SB_HTDOCS|g;
	$fileContent =~ s|__vhostname__|$sb_hostname|g;
	$fileContent =~ s|__port__|$httpd_port|g;
	$fileContent =~ s|__error_log__|$errorLogDir|g;
	$fileContent =~ s|__php_path__|$dirname|g;
	$fileContent =~ s|__php__|$basename|g;

	if (exists($ENV{'PHPRC'}) && defined($ENV{'PHPRC'})) {
		$fileContent =~ s|__phprc_path__|$ENV{'PHPRC'}|g;
		$fileContent =~ s/^#SetEnv/SetEnv/mg
	}

	#get VHOST conf dir
	my $HTTPD_INCLUDE_DIR = SB::OS::getApacheVHostDir();
	unless (-d $HTTPD_INCLUDE_DIR) { run("mkdir -p $HTTPD_INCLUDE_DIR"); }
	$APACHE_CTL_CMD = SB::OS::getApacheStartCmd();
	my $configpath = $HTTPD_INCLUDE_DIR . "/zz011_sitebuilder.conf";
	File::Copy::copy($configpath, "/tmp/zz011_sitebuilder.conf~") if (-e $configpath);

	unless (open (F,"> $configpath")) {
		$Logger->Log(ERROR, "Cannot create virtual host file $configpath: $!");
		return 1;
	}

	unless (SB::OS::hasPleskInstalled()) {
		($output, $code) = run($APACHE_CTL_CMD);
		if ($code && $output !~ /already running/ig) {
			$Logger->Log(WARNING, "Cannot start HTTPD");
			$apacheError = 1;
		}
		if ($httpd_port == 80 || listenHostForPort($host_ip, $httpd_port) == 1) {
			$fileContent =~ s/^Listen/#Listen/mg;
			$Logger->Log(DEBUG, "Removing 'Listen $httpd_port' directive from SB config");
		}
		# On FreeBSD apache may be really hard to start
		sleep(2);
	}
	else {
		$fileContent =~ s/^Listen/#Listen/mg;
	}
	print F $fileContent;
	close(F);
	restorecon($configpath);

	run("a2ensite zz011_sitebuilder.conf") if (SB::OS::getOsName() eq "debian");

	$APACHE_CTL_CMD = SB::OS::getApacheReloadCmd();
	unless (SB::OS::hasPleskInstalled()) {
		$Logger->Log(INFO, "Reloading apache");
		# On some systems apache is having a hard time loading too many
		# configs at once
		(undef, $code) = run("$APACHE_CTL_CMD && $APACHE_CTL_CMD");

		if ($code) {
			$Logger->Log(WARNING, "Cannot reload HTTPD. You may need to run $APACHE_CTL_CMD manually");
			$apacheError = 1;
		}
	} else {
		run($APACHE_CTL_CMD);
	}
	
	if (SB::OS::hasCPanelInstalled()) {
		$Logger->Log(INFO, "CPanel detected. Run ${SB_PREFIX}/utils/cpanel.sh script or manually add $configpath to Apache includes");
	}

	$Logger->Log(ERROR, "There were errors during apache configuration.") if $apacheError;

	return $apacheError;
}

sub createNewSBDatabaseUser {
	my $dbh = shift;
	my $sth;
	my $sbDBname = $CONFIG->{"database"}->{"dbname"};
	my $sbDBuser = $CONFIG->{"database"}->{"username"};
	my $sbDBpasswd = $CONFIG->{"database"}->{"password"};
	my $db_type = $CONFIG->{"database"}->{"adapter"};
	my $db_host = $CONFIG->{"internal"}->{"db_host"};
	my $ext_ip = $CONFIG->{"internal"}->{"ext_ip"};

	my $file = $SB_PREFIX 
				. "/" . $CONFIG->{'path'}->{'sql'}
				. "/" . $SB::Defaults::SQL_ADAPTER{$db_type} . "/system.sql";

	unless (open(F, $file)) {
		$Logger->Log(ERROR, "Cannot open $file for reading: $!");
		return 1;
	}
	while (my $query = <F>) {
		chomp $query;
		$query =~ s|%sb_db_host%|$ext_ip|;
		$query =~ s|%sb_db_user%|$sbDBuser|;
		$query =~ s|%sb_db_password%|$sbDBpasswd|;
		$query =~ s|%sb_db_name%|$sbDBname|;
		$sth = $dbh->prepare($query);
		$Logger->Log(VERYVERBOSE, "Processing sql-query: $query");
		$sth->execute;
		if ($DBI::errstr) {
			$Logger->Log(ERROR, "Result: $DBI::errstr");
			close(F);
			return 1;
		}
	}
	close(F);
	return 0;
}

sub updateSBDatabaseUser {
	my $dbh = shift;
	my $sbDBname = $CONFIG->{"database"}->{"dbname"};
	my $sbDBuser = $CONFIG->{"database"}->{"username"};
	my $sbDBpasswd = $CONFIG->{"database"}->{"password"};
	my $ext_ip = $CONFIG->{"internal"}->{"ext_ip"};

	my @queries = (
		"update user set Password=PASSWORD('$sbDBpasswd') where User='$sbDBuser' and Host='$ext_ip'",
		"update db set Host='$ext_ip' where User='$sbDBuser' and  Db='$sbDBname'",
		"update tables_priv set Host='$ext_ip' where User='$sbDBuser' and  Db='$sbDBname'");

	# Avoid flushing privileges table
	if (!exists($ENV{'MYSQL_NEED_RESTART'}) || $ENV{'MYSQL_NEED_RESTART'} ne "true") {
		push @queries, "FLUSH PRIVILEGES";
	}
	foreach my $query (@queries) {
		my $sth = $dbh->prepare($query);
		$sth->execute();
		$Logger->Log(VERYVERBOSE, $query);
		last if $DBI::errstr;
	}
	if ($DBI::errstr) {
		$Logger->Log(ERROR, "DB error: $DBI::errstr");
		return 1;
	}
	return 0;
}

sub createSBDatabaseUser {
	my $sbDBuser = $CONFIG->{"database"}->{"username"};
	my $ext_ip = $CONFIG->{"internal"}->{"ext_ip"};

	my $sth;
	my @row;

	my $dbh = SB::DB::getMysqlHandle()
			or $Logger->Log(FATAL, "Could not connect to database: $DBI::errstr");

	$sth = $dbh->prepare(qq{select User from user where User="$sbDBuser" and Host="$ext_ip"});
	$sth->execute();
	@row = $sth->fetchrow_array;

	$Logger->Log(INFO, "Creating SiteBuilder database user");
	unless (defined($row[0])) {
		return createNewSBDatabaseUser($dbh);
	}
	else {
		return updateSBDatabaseUser($dbh);
	}
	#return 0;
}

sub _importSqlFile($$) {
	my $dbh  = shift;
	my $sqlfile = shift;

	my $sth;
	my $query = "";
	my $date = strftime "%Y-%m-%dT%H:%S:%M%z", localtime;

	open(F, $sqlfile) || 
		$Logger->Log(FATAL, "Cannot open $sqlfile for reading: $!");
	while (<F>) {
		chomp;
		next if  /^--/;
		next if  /^[\t|\s]*$/;
		my $uuid = getUuid();
		s/sb_admin_password/$CONFIG->{'internal'}->{'sb_admin_passwd'}/;
		s/%uuid%/$uuid/;
		s/%admin_date_create%/$date/;

		$query .= $_;

		if (/;[\t\s]*$/) {
			$Logger->Log(VERYVERBOSE, "Process sql-query: $query");
			$sth = $dbh->prepare($query);
			$sth->execute;
			if ($DBI::errstr) {
				$Logger->Log(ERROR, "DB error: $DBI::errstr");
				return 1;
			}
			$Logger->Log(VERYVERBOSE, "Ok");
			$query = "";
		}
	}
	close(F);
	return 0;
}

# Create SiteBuilder database and fill it with some valuable data
# Returns: 0 on success
#		   1 on error
#		   2 if database already exists
sub createSBDatabase {
	my $sbDBname = $CONFIG->{"database"}->{"dbname"};
	my $db_type = $CONFIG->{'database'}->{"adapter"};
	my @row;
	my $sth;
	my @files;
	my $version;
	my $def_charset="";

	my $dbh = SB::DB::getMysqlHandle();
	unless ($dbh) {
		$Logger->Log(ERROR, "Cannot connect to database: $DBI::errstr");
		return 1;
	}

	$sth = $dbh->prepare("SHOW VARIABLES LIKE 'version';");
	$sth->execute();
	@row = $sth->fetchrow_array;
	$version = $row[1];

	if ($version =~ /^4.1.([2-9]|1[0-9])|^5/) {
		$def_charset = "DEFAULT CHARSET=utf8";
	}
	
	# Ubuntu hack for mysql 4.1
	if ($version =~ /^4\.1\..+ubuntu/i) {
		$CONFIG->{'database'}->{'emulate_prepares'} = 1;
		$Logger->Log(DEBUG, "Enabling emulated prepared statements");
	}
	# ------------------

	$sth = $dbh->prepare("CREATE DATABASE IF NOT EXISTS $sbDBname $def_charset");
	$sth->execute();
	$Logger->Log(FATAL, $DBI::errstr) if $DBI::errstr;
	$Logger->Log(DEBUG, "Created $sbDBname database");
	$dbh->disconnect();

	$dbh = SB::DB::getMysqlHandle();
	updateSBDatabaseUser($dbh);
	$dbh->disconnect();

	$dbh = SB::DB::getSBHandle() or $Logger->Log(FATAL, $DBI::errstr);

	$sth = $dbh->prepare("SHOW TABLES");
	$sth->execute();
	@row = $sth->fetchrow_array;

	return 2 if (defined($row[0]));

	if ($version =~ /^3\.2/ or $version =~ /^4\.0/) {
		push @files, "structure3.sql";
	}
	else {
		push @files, "structure.sql";
	}
	push @files, "data.sql";
	foreach my $file (@files) {
		my $sqlfile;
		$sqlfile = $CONFIG->{'path'}->{"sql"} 
					. "/" . $SB::Defaults::SQL_ADAPTER{$db_type} . "/" . $file;
		if (substr($sqlfile, 0, 1) ne '/') {
			$sqlfile = $SB_PREFIX . "/" . $sqlfile;
		}
		_importSqlFile($dbh, $sqlfile);
	}
	$dbh->disconnect();
	return 0;
}

# Set SiteBuilder admin user password
sub updateAdmin() {
	my $adminPassword = $CONFIG->{"internal"}->{"sb_admin_passwd"};
	my $query;
	unless ($adminPassword) {
		$Logger->Log(DEBUG, "Empty admin password supplied, refusing to change");
		return 1;
	}

	$adminPassword = md5_hex($adminPassword);

	#make system records
	my $dbh = SB::DB::getSBHandle() or 
		$Logger->Log(FATAL, "Could not connect to database: $DBI::errstr");

	$query = qq{select user.id from user_settings, user 
				where user_settings.is_built_in = 1 and user.parent_id = 0 
				and user.user_settings_id = user_settings.id};
	my $sth = $dbh->prepare($query);
	$sth->execute();
	my @row = $sth->fetchrow_array;
	my $admin_id = $row[0];

	$sth = $dbh->prepare(qq{update user set user_password="$adminPassword" where id=$admin_id});
	$sth->execute();
	$Logger->Log(INFO, "Updating administrator credentials");
	$Logger->Log(ERROR, $DBI::errstr) if $DBI::errstr;

	$dbh->disconnect()  or 
		$Logger->Log(WARNING, "Disconnection error: $DBI::errstr");
	return 0;
}

sub updateSitebuilderConf() {
	$Logger->Log(DEBUG, "Updating sitebuilder.conf");
	my $res = parsePlainConfig("/etc/swsoft/sitebuilder/sitebuilder.conf");
	$res->{'PHPCLI_PATH'} = $CONFIG->{'internal'}->{"phpcli_path"};
	$res->{'PHPCGI_PATH'} = $CONFIG->{'internal'}->{"phpcgi_path"};
	return writePlainConfig("/etc/swsoft/sitebuilder/sitebuilder.conf", $res);
}

1;

