# Copyright 1999-2012. Parallels IP Holdings GmbH. All Rights Reserved.
package PleskStructure;

use PleskVersion;
use XmlNode;


# IP address is the pair (address, type)

my %ipType;                #ip -> 'shared'|'exclusive'
my %clientDomains;         #clientName -> domainName
my %clientIds;             #clientId -> clientName

my %clientGuids;           #clientId -> clientGuid

my %clientChilds;          #clientId -> clientName
my %domainIp;              #domainName -> @(ip1, [ip2], ...)
my %domainIds;             #domainName -> id
my %domainAsciiNames;      #domainId -> domainAsciiName
my %domain2Client;         #domainId -> clientId
my %clientData;            #clientName -> { 'id'-> $id, 'type' -> $type, 'parent' -> $parent, 'ip' -> @ip, 'guid' -> $guid }
my %resellerPlans;         #resellerId -> planId
my $adminName;

my $dbh;

my %userLogins;            #login -> %users
my %roleIds;               #id -> %roles

my %sites2Subscriptions;   #siteName -> subscriptionName

my %serviceNodesIps;       #ip -> remote node id

my %serviceNodes;          # id -> output of command service_node --describe-for-pmm-deployer

my %registeredDbServers;   #dbServerId -> { 'admin' => $admin, 'password' => $password }
my $databaseRegistrarUtilFailed = 0;

## public interface

sub init {
  $dbh = shift;

  if ( PleskVersion::isSmb() ) {
    __initSmb();
  } elsif ( PleskVersion::atLeast( 11, 1, 0 ) ) {
    __init111();
  } elsif ( PleskVersion::atLeast( 10, 0, 0 ) ) {
    __init100();
  } elsif (PleskVersion::atLeast(9, 0, 0)) {
    __init90();
  } else {
    __init60();
  } 
}

sub getClients {
  my( @ret );
  foreach my $clientName( keys %clientData ){
    push @ret, $clientName if ${clientData}{$clientName}{'type'} eq 'client';
  }
  return @ret;
}

sub getResellers {
  my( @ret );
  foreach my $clientName( keys %clientData ){
    push @ret, $clientName if ${clientData}{$clientName}{'type'} eq 'reseller';
  }
  return @ret;
}

sub getResellersFromId{
  my ( $ids ) = @_;
  my ( @ret, @wrong );
  foreach my $id( @{$ids} ){
     my $login = getClientNameFromId( $id );
     if( $login ){
        if( getClientType( $login ) eq 'reseller' ) {  push @ret, $login;  }
        else{  push @wrong, $id; }
     }
     else{ push @wrong, $id; }
  }
  @{$ids} = @wrong;
  return @ret;
}

sub getResellerPlanId {
  my ($id) = @_;
  return (exists $resellerPlans{$id}) ? $resellerPlans{$id} : undef;
}

sub getClientsFromId{
  my( $ids )= @_;
  my ( @ret, @wrong );
  foreach my $id( @{$ids} ){
     my $login = getClientNameFromId( $id );
     if( $login ){
        if( getClientType( $login ) eq 'client' ) {  push @ret, $login;  }
        else{  push @wrong, $id; }
     }
     else{ push @wrong, $id; }
  }
  @{$ids} = @wrong;
  return @ret;
}

sub getDomainsFromId{
  my ( $ids ) = @_;
  my ( @ret, @wrong );
  foreach my $id( @{$ids} ){
     my $domain = getDomainNameFromId( $id );
     if( $domain ){
        push @ret, $domain;
     }
     else{ push @wrong, $id; }
  }
  @{$ids} = @wrong;
  return @ret;
}

sub getAdminClients {
  if (PleskVersion::atLeast(9, 0, 0)) {
    my @ret = getMyClients( $adminName );
    #push @ret, $adminName;
    return @ret;
  }
  else{
    return getClients();
  }
}

sub getAdminDomains {
  if (PleskVersion::atLeast(9, 0, 0)) {
     return getDomainsForClient( $adminName );
  }
  else{
    my @ret;
    return @ret;
  }
}

sub getAdminName{
  return $adminName;
}

sub getAdminId{
  return getClientId( $adminName ) if $adminName;
  return;
}

sub getAdminGuid{
  return getClientGuid( $adminName ) if $adminName;
  return "00000000-0000-0000-0000-000000000000";
}

sub getClientsCount{
  my ( $parentName ) = @_;
  my $count = 0;
  if( $parentName ){
     my $parentId = $clientData{$parentName}{'id'};
     foreach my $clientName( keys %clientData ){
       if( $clientData{$clientName}{'parent'} == $parentId ){
          $count += 1;
          if( $clientData{$clientName}{'type'} eq 'reseller' ){
            $count += getClientsCount( $clientData{'id'} );
          }
       }
     }
  }
  else{
    $count = scalar( keys %clientData );
  }
  return $count;
}

sub getDomainsCount{
  my ($parentName ) = @_;
  my $count = 0;
  if( $parentName ){
     my $parentId = $clientData{$parentName}{'id'};
     foreach my $clientName( keys %clientData ){
       if( $clientData{$clientName}{'parent'} == $parentId ){
          $count += scalar( getDomainsForClient( $clientName ) );
          if( $clientData{$clientName}{'type'} eq 'reseller' ){
            $count += getDomainsCount( $clientData{'id'} );
          }
       }
       elsif( $clientData{$clientName}{'id'} == $parentId ){
         $count += scalar( getDomainsForClient( $clientName ) );
       }
     }
  }
  else{
    foreach my $clientName( keys %clientDomains ){
      $count += scalar( getDomainsForClient( $clientName ) )
    }
  }
  return $count;
}

sub PleskStructure::sortClients{
  my ( $a, $b ) = @_;
  return $a cmp $b if $clientData{$a}{'type'} eq $clientData{$b}{'type'};
  return  1 if $clientData{$a}{'type'} eq 'reseller';
  return -1 if $clientData{$b}{'type'} eq 'reseller';
  return  1 if $clientData{$a}{'type'} eq 'admin';
  return -1 if $clientData{$b}{'type'} eq 'admin';
  die "Cannot compare clients '$a', '$b'. Invalid types!";
}


sub getMyClients {
  my ( $parentName ) = @_;
  my( @ret );
  die "Cannot find client '$parentName'!"  if not exists $clientData{$parentName};
  my $parentId = $clientData{$parentName}{'id'};
  return @{$clientChilds{$parentId}} if exists $clientChilds{$parentId};
  return @ret;
}

sub getClientType{
  my( $clientName ) = @_;
  die "Cannot determine client type. Cannot find client '$clientName'!"  if not exists $clientData{$clientName};
  my $ret = $clientData{$clientName}{'type'};
  $ret = 'client' if not $ret;
  return $ret;
}

sub getDomainNameFromId{
  my( $domainId ) = @_;
  my ( $name, $data );
  my $ret = '';
  while ( ($name, $data) = each %domainIds) {
    $ret = $name if $data == $domainId;
  }
  return $ret;
}

sub getDomainAsciiNameFromId{
  my( $domainId ) = @_;
  return $domainAsciiNames{$domainId};
}

sub getClientNameFromId{
  my( $clientId ) = @_;
  return $clientIds{$clientId};
}

sub getClientGuidFromId{
  my( $clientId ) = @_;
  return $clientGuids{$clientId};
}

sub getClientParentId{
  my( $clientName ) = @_;
  return  $clientData{$clientName}{'parent'};
}

sub getClientId{
  my( $clientName ) = @_;
  return  $clientData{$clientName}{'id'};
}

sub getDomainId{
  my( $domainName ) = @_;
  return  $domainIds{$domainName};
}

sub getClientGuid{
  my( $clientName ) = @_;
  return  $clientData{$clientName}{'guid'};
}

sub getDomains {
  return keys %domainIp;
}

sub getDomainsForClient {
  my ($clientName) = @_;
  return @{ $clientDomains{$clientName} };
}

sub getClientIdForDomainId {
  my ($domainId) = @_;
  return $domain2Client{$domainId};
}

sub getClientIps {
  my ($clientName) = @_;
  return %{$clientData{$clientName}{'ip'}};
}

sub getClientIpType {
  my ($clientName, $ip) = @_;
  return ${$clientData{$clientName}{'ip'}}{$ip} if exists ${$clientData{$clientName}{'ip'}}{$ip};
  return $ipType{ $ip };
}

# May return undef in the case of disabled domain without hosting
sub getDomainIp {
  my ($domainName) = @_;
  return $domainIp{$domainName};
}

sub isExclusiveIp {
  my ($ip) = @_;

  return unless defined $ip;

  return $ipType{$ip} eq "exclusive";
}

sub getIpType {
  my ($ip) = @_;

  return unless defined $ip;

  return $ipType{$ip}; 
}

sub getIp4DomainWoHosting {
  my ($domainId, $preferredIp, $typePtr) = @_;
  return unless defined $preferredIp;

  my @result;

  my $clientId = $domain2Client{$domainId};
  my %clientIps = getClientIps(getClientNameFromId($clientId));
  if ( keys %clientIps ) {
    if( grep $_ eq $preferredIp, keys %clientIps) {
      ${$typePtr} = $clientIps{$preferredIp};
      push @result, $preferredIp;
      return \@result;
   }

    foreach my $ip ( keys %clientIps ) {
      if ( $clientIps{$ip} eq 'shared' ) {
        ${$typePtr} = 'shared';
        push @result, $ip;
        return \@result;
      }
    }
    foreach my $ip ( keys %clientIps ) {
      ${$typePtr} = 'exclusive';
      push @result, $ip;
      return \@result;
    }
  }
  ${$typePtr} = 'shared';
  push @result, $preferredIp;
  return \@result;
}

sub getUserLogins {
  my( $ownerLogin ) = @_;
  return unless exists $userLogins{$ownerLogin};

  return keys %{$userLogins{$ownerLogin}};
}

sub getRoleIds {
  my( $ownerLogin ) = @_;
  return unless exists $roleIds{$ownerLogin};

  return keys %{$roleIds{$ownerLogin}};
}

## Plesk Smb

sub __initSmb {
  __init90();
  __populateSmbUsers();
  __populateSmbRoles();
}

## Plesk 11.1 -

sub __init111 {
  __init100();
  __populateServiceNodes();
}

## Plesk 10.0 -

sub __init100 {
  __init90();
  __populateUsers();
  __populateRoles();
  __populateSites();
  __populateResellerPlans();
}

sub __populateResellerPlans {
  my $sql = "SELECT s.object_id, t.id FROM Subscriptions AS s ".
            "INNER JOIN PlansSubscriptions AS p ON s.id = p.subscription_id ".
            "INNER JOIN Templates AS t ON t.id = p.plan_id ".
            "WHERE s.object_type = 'client' AND t.owner_id = (SELECT id FROM clients WHERE type = 'admin' LIMIT 0,1) AND t.type = 'reseller'";

  if ($dbh->execute($sql)) {
    while (my $row = $dbh->fetchrow()) {
      $resellerPlans{$row->[0]} = $row->[1];
    }
    $dbh->finish();
  }
}

## Plesk 9.0 -

sub __init90 {
  __populateIpsFromIPAddressesTable9();
  __populateClientIpPool9();
  __populateDomains();
}

sub __populateIpsFromIPAddressesTable9 {
  my $row;

  if($dbh->execute("SELECT ip_address, id FROM IP_Addresses")) {
    while ( $row = $dbh->fetchrow() ) {
      $ipType{ $row->[0] } = 'shared';
    }
  }
  $dbh->finish();
}


sub __populateClientIpPool9 {
  my $sql = "SELECT c.id, c.login, p.ip_address, c.type, c.parent_id, pl.type, c.guid FROM clients c "
    . "LEFT JOIN ip_pool pl ON c.pool_id = pl.id "
    . "LEFT JOIN IP_Addresses p ON pl.ip_address_id = p.id";
  if ( $dbh->execute($sql) ) {
  my $row;
    while ($row = $dbh->fetchrow()) {
      my ($id, $clientName, $ipAddress, $cltype, $clparent, $iptype, $guid ) = @{$row};
      $clientIds{$id} = $clientName;
      $clientGuids{$id} = $guid;
      $clientData{$clientName} = { 'id' => $id, 'type' => $cltype, 'parent' => $clparent, 'ip' => {}, 'guid' => $guid } if not exists $clientData{$clientName};
      ${$clientData{$clientName}{'ip'}}{$ipAddress} = $iptype if ($ipAddress);
      if( $cltype eq 'admin' ){
        $adminName = $clientName;
        $ipType{$ipAddress} = $iptype;
      }
    }
  }
  foreach my $clientName( keys %clientData ){
     my $clparent = $clientData{$clientName}{'parent'};
     if( $clparent ) {
        $clientChilds{$clparent} = () if not exists $clientChilds{$clparent};
        push @{$clientChilds{$clparent}}, $clientName;
     }
  }
  $dbh->finish();
}

## Plesk 6.0 - 8.0

sub __init60 {
  __populateIpsFromIPAddressesTable();
  __populateClientIpPool();
  __populateDomains();
}

## common

sub __populateUsers {
  my $sql = "SELECT smb_users.login, clients.login FROM smb_users, clients WHERE smb_users.ownerId = clients.id";
  if($dbh->execute($sql)) {
    my $row;
    while ( $row = $dbh->fetchrow() ) {
      my $userLogin = $row->[0];
      my $ownerLogin = $row->[1];
      $userLogins{$ownerLogin}->{$userLogin} = 1;
    }
  }
  $dbh->finish();
}

sub __populateSmbUsers {
  my @logins = @{DAL::selectLoginFromSmbUsers()};
  
  foreach my $login (@logins) {
    $userLogins{'admin'}->{$login} = 1;
  }  
}

sub __populateRoles {
  my $sql = "SELECT smb_roles.id, clients.login FROM smb_roles, clients WHERE smb_roles.ownerId = clients.id";
  if($dbh->execute($sql)) {
    my $row;
    while ( $row = $dbh->fetchrow() ) {
      my $roleId = $row->[0];
      my $ownerLogin = $row->[1];
      $roleIds{$ownerLogin}->{$roleId} = 1;
    }
  }
  $dbh->finish();
}

sub __populateSmbRoles {
  my @roles = @{DAL::selectSmbRoles()};
  
  foreach my $role (@roles) {
    $roleIds{'admin'}->{$role->{'id'}} = 1;
  }
}

sub __populateIpsFromIPAddressesTable {
  my $row;

  if($dbh->execute("SELECT ip_address, type, id FROM IP_Addresses")) {
    while ( $row = $dbh->fetchrow() ) {
      $ipType{ $row->[0] } = $row->[1];
    }
  }
  $dbh->finish();
}

sub __populateClientIpPool {
  my $sql = "SELECT c.id, c.login, p.ip_address FROM clients c "
          . "LEFT JOIN Repository r ON c.pool_id = r.rep_id "
          . "LEFT JOIN IP_Addresses p ON r.component_id = p.id";
  $dbh->execute($sql);
  my $row;
  while ($row = $dbh->fetchrow()) {
    my ($id, $clientName, $ipAddress) = @{$row};
    $clientData{$clientName} = { 'id' => $id, 'type' => 'client', 'parent' => 0, ip => {} } if not exists $clientData{$clientName};
    ${$clientData{$clientName}{'ip'}}{$ipAddress} = $ipType{$ipAddress} if $ipAddress;
    $clientIds{$id} =  $clientName;
  }
    $dbh->finish();
}

sub __populateDomains {
  my $domainNameColumn = 'displayName';

  my $sql;

  if ( PleskVersion::atLeast( 10, 2, 0 ) and not PleskVersion::isSmb() ) {
    $sql = "SELECT DISTINCT BINARY domains.$domainNameColumn, IP_Addresses.ip_address FROM DomainServices, IpAddressesCollections, IP_Addresses, domains"
             . " WHERE DomainServices.dom_id = domains.id AND DomainServices.ipCollectionId = IpAddressesCollections.ipCollectionId"
             . " AND IpAddressesCollections.ipAddressId = IP_Addresses.id AND domains.webspace_id = 0 ";
  } elsif ( PleskVersion::atLeast( 10, 0, 0 ) and not PleskVersion::isSmb() ) {
    $sql = "SELECT d.$domainNameColumn, p.ip_address FROM domains d, IP_Addresses p, hosting h "
             . "WHERE p.id = h.ip_address_id AND h.dom_id = d.id AND d.webspace_id = 0";
  }
  else {
    $sql = "SELECT d.$domainNameColumn, p.ip_address FROM domains d, IP_Addresses p, hosting h "
             . "WHERE p.id = h.ip_address_id AND h.dom_id = d.id";
  }
  if($dbh->execute($sql)) {
    my $row;
    while ( $row = $dbh->fetchrow() ) {
      my ( $domainName, $ipAddress ) = @{$row};
      if (!exists($domainIp{$domainName})) {
        @{$domainIp{$domainName}} = ($ipAddress);
      }else {
        push  @{$domainIp{$domainName}}, $ipAddress;
      }
    }
  }
  $dbh->finish();

  
  if ( !PleskVersion::atLeast( 10, 2, 0 ) ) {
  
    if ( PleskVersion::atLeast( 10, 0, 0 ) and not PleskVersion::isSmb() ) {
      $sql = "SELECT d.$domainNameColumn, p.ip_address FROM domains d, IP_Addresses p, forwarding f "
            . "WHERE p.id = f.ip_address_id AND f.dom_id = d.id AND d.webspace_id = 0";
    }
    else {
      $sql = "SELECT d.$domainNameColumn, p.ip_address FROM domains d, IP_Addresses p, forwarding f "
            . "WHERE p.id = f.ip_address_id AND f.dom_id = d.id";
    }
    if($dbh->execute($sql)) {
      while ( $row = $dbh->fetchrow() ) {
        my ( $domainName, $ipAddress ) = @{$row};
        if (!exists($domainIp{$domainName})) {
          @{$domainIp{$domainName}} = ($ipAddress);
        }else {
          push  @{$domainIp{$domainName}}, $ipAddress;
        }
      }
    }
    $dbh->finish();

  }

  if ( PleskVersion::atLeast( 10, 0, 0 ) and not PleskVersion::isSmb() ) {
    $sql = "SELECT d.$domainNameColumn FROM domains d WHERE htype = 'none' AND webspace_id = 0";
  }
  else {
    $sql = "SELECT d.$domainNameColumn FROM domains d WHERE htype = 'none'";
  }
  if($dbh->execute($sql)) {
    while ( $row = $dbh->fetchrow() ) {
      $domainIp{ $row->[0] } = undef;
    }
  }
  $dbh->finish();

  if ( PleskVersion::atLeast( 10, 0, 0 ) and not PleskVersion::isSmb() ) {
    $sql = "SELECT d.$domainNameColumn, d.name, c.login, c.id, d.id dom_id FROM clients c LEFT JOIN domains d ON d.cl_id = c.id WHERE d.webspace_id = 0";
  }
  else {
    $sql = "SELECT d.$domainNameColumn, d.name, c.login, c.id, d.id dom_id FROM clients c LEFT JOIN domains d ON d.cl_id = c.id";
  }
  if($dbh->execute($sql)) {
    while ( $row = $dbh->fetchrow() ) {
      my ( $domainName, $domainAsciiName, $clientName, $clientId, $domainId ) = @{$row};
      $domainIds{$domainName} = $domainId;
      $domainAsciiNames{$domainId} = $domainAsciiName;
      $domain2Client{$domainId} = $clientId;
      if ( !exists( $clientDomains{$clientName} ) ) {
        $clientDomains{$clientName} = [];
      }
      if ($domainName) {
        push @{ $clientDomains{$clientName} }, $domainName;
      }
    }
  }
  $dbh->finish();
}

sub __populateSites {
  my $sql = "SELECT a.displayName, b.displayName FROM domains a INNER JOIN (SELECT id, displayName FROM domains) b WHERE a.webspace_id = b.id";
  if($dbh->execute($sql)) {
    while ( $row = $dbh->fetchrow() ) {
      my ($site, $subscription) = @{$row};
      $sites2Subscriptions{$site} = $subscription;
    }
  }
  $dbh->finish();
}

sub getSites {
  return \%sites2Subscriptions;
}

sub __populateServiceNodes {
  eval {require XML::Simple; 1;};
  my $serviceNodeCommand = AgentConfig::getServiceNodeCommand();
  my $cmd = $serviceNodeCommand . " --describe-for-pmm-deployer";
  Logging::debug("Exec: " . $cmd);
  my $result = `$cmd`;
  chomp($result);

  my $xs = XML::Simple->new(ForceArray => 1);

  my $ref = $xs->XMLin($result, KeyAttr => []);
  
  foreach my $serviceNode (@{$ref->{'service-node'}}) {
    if ($serviceNode->{'local'} eq 'false') {
      $serviceNodes->{$serviceNode->{'database-id'}} = $serviceNode;
      foreach my $ipaddress (@{$serviceNode->{'ip-address'}}) {
        $serviceNodesIps{$ipaddress} = $serviceNode->{'database-id'};
      }
    }
  }
}

sub isRemoteIp {
  my $ip = shift;
  return exists($serviceNodesIps{$ip}); 
}

sub getRemoteNodeId {
  my $ip = shift;
  return $serviceNodesIps{$ip};
}

sub getCommunicationIp {
  my $ip = shift;
  my $serviceNodeId = getRemoteNodeId($ip);  
  return $serviceNodes->{$serviceNodeId}->{'communication-ip-address'};
}

sub getServiceNodeProperty {
  my ($serviceNodeId, $propertyName) = @_;
  
  my $serviceNode = $serviceNodes->{$serviceNodeId};
  
  foreach my $properties (@{$serviceNode->{'properties'}}) {
      foreach my $property (@{$properties->{'property'}}) {
          if ($property->{'name'}[0] eq $propertyName) {
              return $property->{'value'}[0];
          }
      }
  }
}

sub __populateDbServers {
  my $util = AgentConfig::getDatabaseRegistrarUtil();
  my $cmd = $util . " --get-all-registered-servers";
  Logging::debug(" Exec: " . $cmd);
  my $result = `$cmd`;
  chomp($result);
  
  my $retCode = $? >> 8;
  if( $retCode != 0 ) {
    Logging::error( "Unable to get list of db servers (ErrorCode: $retCode, STDOUT:$result)." ,'UtilityError');
    $databaseRegistrarUtilFailed = 1;
    return;
  }
  
  my @db_servers;
  eval($result);
  
  if ( $@ ) {
    Logging::error( "Unable to parse db servers from utility output ($@):\n $result", 'UtilityError' );
    return;
  }
  
  foreach my $dbServer (@db_servers) {
    my $xmlNode = HelpFuncs::hash2XmlNode($dbServer, 1);
    $registeredDbServers{ $xmlNode->getChild('id')->getText() } = { 'admin' => $xmlNode->getChild('admin')->getText(), 'password' => $xmlNode->getChild('password')->getText() };
  }
}

sub getDbServerAdminPassword {
  my ( $dbServer ) = @_;
  
  my $dbServerId = $dbServer->{'id'};
  
  if ( PleskVersion::atLeast( 11, 0, 4 ) ) {
  
    if ($databaseRegistrarUtilFailed) {
      return undef;
    }
  
    if ( !%registeredDbServers ) {
      __populateDbServers();
    }
  
    return $registeredDbServers{ $dbServerId }{'password'};

  } else {
  
    if ( $dbServer->{'type'} eq "mysql" and $dbServer->{'host'} eq 'localhost' ) {
      $dbPasswd = AgentConfig::get('password');
    } else {
      $dbPasswd = $dbServer->{'admin_password'};
    }
    return $dbPasswd;

  }
}

1;

# Local Variables:
# mode: cperl
# cperl-indent-level: 2
# indent-tabs-mode: nil
# tab-width: 4
# End:
