# Copyright 1999-2012. Parallels IP Holdings GmbH. All Rights Reserved.
use strict;
use warnings;

package LimitsAndTemplates;

use Dumper;
use HashUtils;
use ConfixxConfig;

use constant CONFIXX_RESOURCE_UNLIMITED => -1;
use constant PLESK_RESOURCE_UNLIMITED => -1;

# <doc>
# = Limits, permissions and templates =
# </doc>

sub getResellerLimits {
  my ($reseller, $numberOfSystemDomains) = @_;
  my $resellerInfo = Dumper::getResellerInfo($reseller);
  my $maxDomains = ($resellerInfo->{'maxdomains'} == CONFIXX_RESOURCE_UNLIMITED
    || $resellerInfo->{'maxsubdomains'} == CONFIXX_RESOURCE_UNLIMITED) ? PLESK_RESOURCE_UNLIMITED
            : $resellerInfo->{'maxdomains'} + $resellerInfo->{'maxsubdomains'} + $numberOfSystemDomains;

  return HashUtils::mergeHashes(
    _mapBytesLimit(
      HashUtils::hashSlice($resellerInfo,
        'maxkb', 'maxtransfer', 'popmaxkb')
    ),
    _mapInstancesLimit(
      HashUtils::hashSlice($resellerInfo,
        'maxkunden', 'maxpop', 'maxftp',
        'maxmaillist', 'maxmysql'
      )
    ),
    _getConfixxGlobalLimits(),
    {
      'max_dom' => $maxDomains,
      'max_dom_aliases' => 0,
      # wildcards are migrated to subdomains (while normal domains and subdomains are migrated to sites)
      # so for subdomains we should set limit that is not less than wildcards limit
      'max_subdom' => _convertInstancesLimitValue($resellerInfo->{'maxwildcards'}), 
      'overuse' => _getOverusePolicy($resellerInfo),
    }
  );
}

sub getResellerPermissions {
  my ($reseller) = @_;

  my $resellerInfo = Dumper::getResellerInfo($reseller);

  my $defaultPermissions = {
    'cp_access' => 1,
    'create_clients' => 1, # by default in Confixx reseller may create clients
    'create_domains' => 1, # and clients with domains as well
    'manage_phosting' => 1, # physical hosting parameters are managed by reseller in Confixx
    'manage_subdomains' => 1,
    'manage_domain_aliases' => 0,
    'manage_maillists' => 1,
    'allow_insecure_sites' => 1,
    # always allow oversell as Confixx has per-limit option 'hard limit' that prohibits overselling of specified resource,
    # but in Plesk there is only permission for all resources (we can set it to 0 only if overselling allowed for all)
    'allow_oversell' => 1
  };

  # map for values in Confixx, that could be used directly as a boolean permission values
  my %directMap = (
    'maxcronjobs' => 'manage_crontab', # in Confixx it is a limit, all non-zero values including unlimited (-1) makes crontab enabled in Plesk
    'spamfilter' => 'manage_spamfilter',
    'dns' => 'manage_dns',
    'ftp' => 'manage_subftp'
  );

  my $specialPermissions = {
    'manage_php_safe_mode' => Dumper::canResellerEditPhpSafeMode($reseller)
  };

  return HashUtils::mergeHashes(
    $defaultPermissions,
    $specialPermissions,
    HashUtils::replaceKeys(HashUtils::hashSlice($resellerInfo, keys(%directMap)), \%directMap),
    _getBackupPermissions($resellerInfo->{'backup'}),
    _getShellPermissions($resellerInfo->{'shell'}),
    _getStatisticsPermissions($resellerInfo->{'awstats'}, $resellerInfo->{'statistik'}),
    _getAbsentFeaturesPermissions()
  );
}

sub getClientLimits {
  my ($client, $numberOfSites) = @_;

  my $clientInfo = Dumper::getClientInfo($client);
  my $maxSites = ($clientInfo->{'maxsubdomains'} == CONFIXX_RESOURCE_UNLIMITED) ? PLESK_RESOURCE_UNLIMITED
                    : $numberOfSites + $clientInfo->{'maxsubdomains'};

  return HashUtils::mergeHashes(
    _mapBytesLimit(
      HashUtils::hashSlice($clientInfo,
        'maxkb', 'maxtransfer', 'popmaxkb')
    ),
    _mapInstancesLimit(
      HashUtils::hashSlice($clientInfo,
        'maxpop', 'maxftp',
        'maxmaillist', 'maxmysql'
      )
    ),
    _getClientDefaultLimits(),
    _getConfixxGlobalLimits(),
    {
      # Plesk sets default value of customer's traffic limit to 100GB (for resellers and templates it is still unlimited).
      # However Confixx always uses soft quota, and there is no hard traffic quota.
      # So we have to set it in explicit way.
      'max_traffic' => PLESK_RESOURCE_UNLIMITED,
      # also default value problem for expiration date - it is = current date + 1 year in Plesk by default
      'expiration' => PLESK_RESOURCE_UNLIMITED,
      'max_site' => $maxSites,
      # wildcards are migrated to subdomains (while normal domains and subdomains are migrated to sites)
      # so for subdomains we should set limit that is not less than wildcards limit
      'max_subdom' => _convertInstancesLimitValue($clientInfo->{'wildcard'}),
      'overuse'    => 'block', # Confixx user limits are always 'hard'
    }
  );
}

sub getClientPermissions {
  my ($client) = @_;
  my $clientInfo = Dumper::getClientInfo($client);

  my $specialPermissions = {
    # this doesn't really control ability to edit PHP safe mode option,
    # as "Hosting settings management" is off by defaults, which implies this
    # option to be off, however if "Hosting settings management" turns on,
    # "PHP safe mode management" will get correct initial value
    'manage_php_safe_mode' => Dumper::canClientEditPhpSafeMode($client),
  };

  return HashUtils::mergeHashes(
    _getClientDefaultPermissions(),
    $specialPermissions,
    _getClientDirectlyMappedPermissions(
      HashUtils::hashSlice($clientInfo, 'maxcronjobs', 'spamfilter', 'ftp'),
     ),
    _getBackupPermissions($clientInfo->{'backup'}),
    _getShellPermissions($clientInfo->{'shell'}),
    _getStatisticsPermissions($clientInfo->{'awstats'}, $clientInfo->{'statistik'}),
    _getAbsentFeaturesPermissions()
  );
}

# <doc>
# migrate into <domain-template> object(s)
# == Not migrated ==
# Registration e-mail template settings of a customer template are not migrated.
#
# ''Reason'': there is no such feature in Plesk.
#
# This includes:
# * "Subject" (emailbetreff in DB)
# * "Text" (emailtext in DB)
# * "Sender's e-mail address" (emailemailadresse in DB)
# * "Name of sender" (emailname in DB)
# * "CC-recipient" (emailbcc in DB)
#
# Several features like WebFTP, WAP, PHP upload, Directory listing and several others are not migrated too.
#
# ''Reason'': there is no such features in Plesk.
# </doc>
sub getClientTemplates {
  my ($reseller) = @_;

  my @result;
  my @templates = Dumper::getClientTemplates($reseller);

  my %servicesMap = (
    'php' => 'php',
    'perl' => 'perl',
    'ssi' => 'ssi',
    'coldfusion' => 'coldfusion',
    'modpython' => 'python',
    'webmail' => 'webmail',
    'fehlerseiten' => 'err_docs',
    'asp' => 'asp'
  );

  foreach my $template (@templates) {
    my %services = map { $servicesMap{$_} => $template->{$_} } keys %servicesMap;
    # In Confixx, when a client is created from template, it is obligatory to create at least one domain for his subscription. Therefore on Plesk side, the limit for domains should be at least 1. We set the minimum value.
    my $maxSites = 1;
    my $maxSubDomains = CONFIXX_RESOURCE_UNLIMITED; 
    if(CONFIXX_RESOURCE_UNLIMITED != $template->{'maxsubdomains'}
        && CONFIXX_RESOURCE_UNLIMITED != $template->{'wildcard'}) {
      # Wildcard domains are migrated to subdomains.
      $maxSubDomains = $template->{'maxsubdomains'} + $template->{'wildcard'};
    }

    my $booleanTemplateItems = HashUtils::mergeHashes(
      _getAbsentFeaturesPermissions(),
      _getBackupPermissions($template->{'backup'}),
      _getShellPermissions($template->{'shell'}),
      _getStatisticsPermissions($template->{'awstats'}, $template->{'statistik'}),
      _getClientDirectlyMappedPermissions(
        HashUtils::hashSlice($template, 'maxcronjobs', 'spamfilter', 'ftp')
      ),
      \%services
    );

    my %booleanTemplateItemsConverted = map {
      $_ => $booleanTemplateItems->{$_} ? 'true' : 'false'
    } keys %$booleanTemplateItems;

    my $templateItems = HashUtils::mergeHashes(
      _mapBytesLimit(HashUtils::hashSlice($template, 'maxkb', 'maxtransfer', 'popmaxkb')),
      _getClientDefaultLimits(),
      _getConfixxGlobalLimits(),
      _mapInstancesLimit(
        HashUtils::hashSlice($template,
          'maxpop', 'maxftp',
          'maxmaillist', 'maxmysql'
        )
      ),
      \%booleanTemplateItemsConverted,
      {
        # enable physical hosting (by default it is disabled), in Confixx it is always enabled
        'vh_type'   => 'physical',
        'max_site'  => _convertInstancesLimitValue($maxSites),
        'max_subdom' => _convertInstancesLimitValue($maxSubDomains)
      }
    );

    # Workaround for Plesk bug
    # <doc>
    # === Access application catalog ===
    # Access application catalog permission is not set in templates due to a Plesk bug: CLI utility doesn't have this permission, while Web UI has.
    # </doc>
    if (exists($templateItems->{'access_appcatalog'})) {
      delete $templateItems->{'access_appcatalog'};
    }

    push(@result, {
        'name' => $template->{'name'},
        'items' => $templateItems
    });
  }

  return @result;
}

sub _getClientDefaultPermissions {
  return {
    'cp_access' => 1,
    'create_domains' => 0, # in Confixx user is not allowed to create domains by himself
    'manage_phosting' => 0, # physical hosting parameters managing is disabled: in Confixx user can not enable/disable SSI, PHP, CGI support by himself
    'manage_dns' => 0, # user is not allowed to edit DNS in Confixx, DNS is a reseller wide option
    'manage_subdomains' => 1, # user can always manage subdomains in Confixx (still there is a limit for them)
    'manage_domain_aliases' => 0,
    'manage_maillists' => 1,
    'allow_insecure_sites' => 0 # prohibit from changing options, that user was not able to change in Confixx
  };
}

sub _getClientDefaultLimits {
  return {
    'max_site_builder' => 0,
    'max_dom_aliases' => 0
  };
}

sub _getClientDirectlyMappedPermissions {
  my ($values) = @_;

  my %directMap = (
    'maxcronjobs' => 'manage_crontab',
    'spamfilter' => 'manage_spamfilter',
    'ftp' => 'manage_subftp'
  );

  return HashUtils::replaceKeys($values, \%directMap);
}


sub _mapInstancesLimit {
  my ($confixxValuesHash) = @_;
  my %convertedValues = map { $_ => _convertInstancesLimitValue($confixxValuesHash->{$_}) } keys %$confixxValuesHash;

  my $result = HashUtils::replaceKeys(
    \%convertedValues,
    {
      # Confixx name => Plesk name
      'maxkunden' => 'max_cl',
      'maxpop' => 'max_box',
      'maxftp' => 'max_subftp_users',
      'maxmaillist' => 'max_maillists',
      'maxmysql' => 'max_db'
    }
  );

  return $result;
}

sub _mapBytesLimit {
  my ($confixxValuesHash) = @_;

  if (!ConfixxConfig::getValue('mail_quota')) {
    delete($confixxValuesHash->{'popmaxkb'});
  }

  my %convertedValues = map { $_ => convertBytesLimitValue($confixxValuesHash->{$_}) } keys %$confixxValuesHash;

  return HashUtils::replaceKeys(
    \%convertedValues,
    {
      # Confixx name => Plesk name
      'maxkb' => 'disk_space',
      'maxtransfer' => 'max_traffic_soft',
      'popmaxkb' => 'total_mboxes_quota'
    }
  );
}

sub convertBytesLimitValue {
  my ($confixxValue) = @_;

  if ($confixxValue == CONFIXX_RESOURCE_UNLIMITED) {
    return PLESK_RESOURCE_UNLIMITED;
  } else {
    return $confixxValue * 1024;
  }
}

sub _convertInstancesLimitValue {
  my ($confixxValue) = @_;

  if ($confixxValue == CONFIXX_RESOURCE_UNLIMITED) {
    return PLESK_RESOURCE_UNLIMITED;
  } else {
    return $confixxValue;
  }
}


sub _getBackupPermissions {
  my ($confixxBackupValue) = @_;

  use constant CONFIXX_BACKUP_NO => 0;
  use constant CONFIXX_BACKUP_YES => 1;
  use constant CONFIXX_BACKUP_AND_UPLOAD => 2;

  my %result;

  $result{'allow_local_backups'} =
    $confixxBackupValue == CONFIXX_BACKUP_YES ||
    $confixxBackupValue == CONFIXX_BACKUP_AND_UPLOAD;

  $result{'allow_ftp_backups'} =
    $confixxBackupValue == CONFIXX_BACKUP_YES;

  return \%result;
}

# <doc>
# === Shell permission ===
# Only "standard" value makes full shell access (not just a chroot) enabled in Plesk.
# "Scp/SFTP" value is not migrated. ''Reason'': no such feature in Plesk.
# </doc>
# Shell Confixx values:
# - "No". Values in database: shell=0, scponly=0. No shell access at all.
# Map to disabled manage_sh_access permission.
# - "standard". Values in database: shell=1, scponly=0. Full shell access.
# Map to enabled manage_sh_access and manage_not_chroot_shell permissions.
# - "Scp/SFTP". Values in database: shell=0, scponly=1. Shell without permissions to execute files remotely.
# Map to disabled manage_sh_access and manage_not_chroot_shell permissions (Scp/SFTP is not supported by Plesk).
sub _getShellPermissions {
  my ($confixxShellValue) = @_;

  my $manageShellAccess = $confixxShellValue == 1;

  return {
    'manage_sh_access' => $manageShellAccess,
    'manage_not_chroot_shell' => $manageShellAccess
  };
}

# <doc>
# === Statistics permission ===
# Any of "Webalizer / Stats" or "AWstats / Stats" makes manage_webstat permission enabled.
# </doc>
sub _getStatisticsPermissions {
  my ($confixxAwstatsValue, $confixxWebalizerValue) = @_;

  return {
    'manage_webstat' =>
      $confixxAwstatsValue == 1 || $confixxWebalizerValue == 1
  };
}

# <doc>
# === Confixx absent features ===
# Permissions for features that are not presented in Confixx, but presented in Plesk:
# disable all of these features. This includes: application catalog, remote API and many more.
# </doc>
sub _getAbsentFeaturesPermissions {
  my %result = map { $_ => 0 } (
    'manage_log',
    'manage_anonftp',
    'manage_webapps',
    'select_db_server',
    'remote_access_interface',
    'manage_virusfilter',
    'manage_performance',
    'access_appcatalog',
    'manage_quota'
  );

  return \%result;
}

#
# Get limits from admin settings
#
sub _getConfixxGlobalLimits {
  my $adminInfo = Dumper::getAdminInfo();
  my %result;

  if (ConfixxConfig::getValue('mail_quota')) {
    $result{'mbox_quota'} = convertBytesLimitValue($adminInfo->{'popmaxkbhard'})  # from CP Settings->Quota->Hard quota
  }

  return \%result;
}

sub _getOverusePolicy {
  my $resellerInfo = shift;
  my @limits = grep /limit$/, keys %$resellerInfo;
  my $hardLimitsCount = grep { 1 == $resellerInfo->{$_} } @limits;
  if ($hardLimitsCount == 0) {
    return 'normal';
  } elsif ($resellerInfo->{'maxkblimit'} == 0) { # there are hard limits, but disk quota is not set to 'hard'
    return 'not_suspend';
  } else {
    return 'block';
  }
}
1;
