# Copyright 1999-2016. Parallels IP Holdings GmbH. All Rights Reserved.
package Packer;

use strict;
eval{require warnings;1;};

use Status;
use HelpFuncs;
use Storage::FileNameCreator;
use ArchiveContent::ArchiveContent;
use RsyncContent;
use CommonPacker;
use Mailman;
use Logging;
use PerlMD5;
use XPath;
use File::Temp;
use File::Basename qw(dirname);
use IPC::Run qw(run);
use Fcntl qw< S_IROTH S_IWOTH S_ISDIR S_ISLNK >;

use vars qw|@ISA|;

my $DEBUG = undef;

### Common function ###

sub new {
  my $self = {};
  bless( $self, shift );
  $self->_init(@_);
  return $self;
}

sub _init {
  my ( $self, $version, $storagePolicy ) = @_;

  XmlNode::resetCompatibilityProcs();

  $self->{storage} = $storagePolicy;
  $self->{version} = $version;
  $self->{base64} = HelpFuncs::makeMIMEBase64();
  $self->{fnamecreator} = Storage::FileNameCreator->new();
  $self->setBackupProfileFileName( 'backup' );
  $self->{listingOnlyMode} = 0; # Enabled, while getting source content listing for migration
  $self->{capabilityOnlyMode} = 0; # Enabled, while getting capability info for migration
  $self->{migrationMode} = 0; # Enabled, in case of migration, but disabled for backup
  $self->{ownerGuid} = '';
  $self->{ownerType} = '';
  $self->{roots} = [];
  $self->{content_transport_type} = 'archive';
  $self->{content_transport_os} = 'unix';
  $self->{content_transport} = ArchiveContent::ArchiveContent->new( $self->{storage}, $self );
  $self->{decrypt_full_dump} = 0;
  $self->{apache_user} = AgentConfig::getApacheUserInfo();
  $self->{mailman_user} = AgentConfig::getMailmanUserInfo();
  $self->{incrementalCreationDate} = undef;
  $self->{lastIndexPath} = undef;
  $self->{relatedDumps} = ();
  
  $self->flushDumpStatistics();
}

sub flushDumpStatistics {
  my ($self) = @_;
  
  $self->{stat} = ();
  $self->{stat}{vhostDumpsCount} = 0;
  $self->{stat}{vhostSizeOnFS} = 0;
  $self->{stat}{vhostSizeDumped} = 0;
  
  $self->{stat}{dbDumpsCount} = 0;
  $self->{stat}{dbSizeOnFS} = 0;
  $self->{stat}{dbSizeDumped} = 0;
  
  $self->{stat}{mailDumpsCount} = 0;
  $self->{stat}{mailSizeOnFS} = 0;
  $self->{stat}{mailSizeDumped} = 0;
}

sub setContentTransport {
  my ($self, $contentTransportType, $osType) = @_;

  if ( $self->{content_transport_type} ne $contentTransportType or $self->{content_transport_os} ne $osType) {
    $self->{content_transport_type} = $contentTransportType;
    $self->{content_transport_os} = $osType;

    if ( $contentTransportType eq 'archive' ) {
      $self->{content_transport} = ArchiveContent::ArchiveContent->new($self->{storage}, $self);
    }

    if ( $contentTransportType eq 'rsync' ){
      $self->{content_transport} = RsyncContent->new($self->{storage}, $self, @_);
    }
  }
}

sub getContentTransport {
  my ($self) = @_;
  return $self->{content_transport};
}

sub getContentTransportType {
  my ($self) = @_;
  return $self->{content_transport_type};
}

sub setBackupProfileFileName{
  my ($self, $profileName, $profileId ) = @_;
  $self->{backupname} = $self->{fnamecreator}->normalize_long_string( $profileName, $profileId );
  $profileId = '' if not defined $profileId;
  Logging::debug( "Set backup file name '$self->{backupname}' (profile '$profileName', id='$profileId')\n" );
  $self->{backupPrefix} = $profileName;
}

sub setIncrementalCreationDate{
  my ($self, $incrementalCreationDate) = @_;
  $self->{fnamecreator}->setIncrementalCreationDate($incrementalCreationDate);
  $self->{incrementalCreationDate} = $incrementalCreationDate;
}

sub setLastIndexPath{
  my ($self, $lastIndexPath) = @_;
  $self->{lastIndexPath} = $lastIndexPath;
}

sub setBackupOwnerGuid{
  my ($self, $ownerGuid, $ownertype ) = @_;
  $ownerGuid = '' if not $ownerGuid;
  $self->{ownerGuid} = $ownerGuid;
  $ownertype = '' if not $ownertype;
  $self->{ownerType} = $ownertype;
  Logging::debug( "Set backup owner guid '$ownerGuid', type '$ownertype'\n" );
}

sub getBackupOwnerGuid{
  my ($self) = @_;
  return $self->{ownerGuid};
}

sub startCollectStorageStatistics {
  my $self = shift;

  $self->{storage}->startCollectStatistics();
}

sub getStorageStatistics {
  my $self = shift;

  return $self->{storage}->getStatistics();
}

sub setRoot {
  my ( $self, $description, $content, $dumpformat, $adminGuid ) = @_;

  unless ($self->{capabilityOnlyMode}) {
    $self->{root} = XmlNode->new(
      'migration-dump',
      'attributes' => {
        'agent-name' => 'PleskX',
        'dump-version' => $self->{version}
      }
    );
    $self->{root}->setAttribute( 'content-included', $content ? 'true' : 'false' ) if defined $content;
    $self->{root}->setAttribute( 'dump-format', $dumpformat ) if defined $dumpformat;
  } else {
    $self->{root} = XmlNode->new( 'capability-dump',)
  }

  my $dumpinfo = $self->{root}->getChild( 'dump-info', 1 );
  unless ($self->{capabilityOnlyMode}) {
    if ($description) {
      $dumpinfo->addChild( XmlNode->new( 'description', 'content' => $description ) );
    }
  
    my $osDescriptionNode = XmlNode->new('os-description');
    $osDescriptionNode->setAttribute('type', 'unix');
    if (my $apacheUid = getpwnam($self->{apache_user}{'user'})) {
      $osDescriptionNode->setAttribute('apache-uid', $apacheUid);
    }
    if (my $apacheGid = getgrnam($self->{apache_user}{'group'})) {
      $osDescriptionNode->setAttribute('apache-gid', $apacheGid);
    }
    if (my $mailmanUid = getpwnam($self->{mailman_user}{'user'})) {
      $osDescriptionNode->setAttribute('mailman-uid', $mailmanUid);
    }
    if (my $mailmanGid = getgrnam($self->{mailman_user}{'group'})) {
      $osDescriptionNode->setAttribute('mailman-gid', $mailmanGid);
    }
    $dumpinfo->addChild($osDescriptionNode);
  
    my $cpDescriptionNode = XmlNode->new('cp-description');
    my $componentsInstalledNode = XmlNode->new('components-installed');
    my %packages = %{DAL::getSoftwarePackages()};
    while (my ($name, $version) = each(%packages)) {
      my $componentNode = XmlNode->new('component');
      $componentNode->setAttribute('name', $name);
      $componentsInstalledNode->addChild($componentNode);
    }
    $cpDescriptionNode->addChild($componentsInstalledNode);
    $dumpinfo->addChild($cpDescriptionNode);

    my $contentTransportDescriptionNode = $self->{content_transport}->getContentTransportDescription();
    if ( defined $contentTransportDescriptionNode ) {
      my $contentTransportNode = XmlNode->new( 'content-transport' );
      $contentTransportNode->addChild( $contentTransportDescriptionNode );
      $dumpinfo->addChild ( $contentTransportNode );
    }
  }

  if ($adminGuid) {
    $dumpinfo->addChild(XmlNode->new('server-id', 'content' => $adminGuid));
  }
}

sub _getRootForStorageFinish {
  my ($self, $dumpDirPath) = @_;
  
  my $dumpinfo = $self->{root}->getChild('dump-info', 0);
  $dumpinfo->removeChildren('related-dumps');

  if (exists $self->{relatedDumps}->{$dumpDirPath}) {
    my $relatedDumpsNode = XmlNode->new('related-dumps');
    $dumpinfo->addChild($relatedDumpsNode);
    foreach my $relatedDump ( @{$self->{relatedDumps}->{$dumpDirPath}} ) {
      $relatedDumpsNode->addChild(XmlNode->new('related-dump', 'content' => $relatedDump));
    }
  }
  return $self->{root};
}

sub turnOnMigrationMode{
  my ( $self) = @_;
  $self->{migrationMode} = 1;
}

sub turnOnListingOnlyMode{
  my ($self) = @_;
  $self->{listingOnlyMode} = 1;
  Logging::debug( "The listing only mode mode is turned on" );
}

sub turnOnCapabilityOnlyMode{
  my ($self) = @_;
  $self->{capabilityOnlyMode} = 1;
  Logging::debug( "The capability only mode mode is turned on" );
}

sub setDecryptFullDump {
  my ($self) = @_;
  $self->{decrypt_full_dump} = 1;
  Logging::debug( "Set to decrypt full dump" );
}

sub turnOffContent{
  my ($self) = @_;
  $self->{skip_content} = 1;
  Logging::debug( "The dump of content is switched off" );
}

sub isRootNode{
  my ($self, $node) = @_;
  foreach my $root( @{$self->{roots}} ){
    return 1 if $root==$node;
  }
  return 0;
}

sub finishChild {
    my ($self, $node, $path, $fileName ) = @_;
    my $ret;
    if( !$self->{migrationMode} && scalar( @{$self->{roots}} )==1  && $self->isRootNode($node) ) {
      $ret = $self->{storage}->finish( $self->_getRootForStorageFinish($path), $path, $fileName );
      die "Cannot create main dump file!" if $ret!=0;
      $ret = $self->{storage}->getMainDumpXmlRelativePath();
    }
    else {
      $ret = $self->{storage}->finishChild( $self->_getRootForStorageFinish($path), $node, $path, $fileName );
    }
    return $ret;
}

sub _decryptDump {
  my ( $self, $file ) = @_;

  eval {
    my $encryptCmd = AgentConfig::getEncryptUtil();
    push(@{$encryptCmd}, '--decrypt-by-plesk', '-single-file', $file);
    Logging::debug("Execute: @{$encryptCmd}");
    my $stderr;
    IPC::Run::run($encryptCmd, '2>', \$stderr) or die($stderr);
  };
  if ($@) {
    Logging::error("Cannot encrypt dump file (this is not fatal error!): $@", 'UtilityError');
  }
}

sub finishDomains {
  my ($self, $fileName, $fh, $usersContentSize, $infoxml) = @_;

  if ( exists $self->{domainNodes} ) {
    my ( $id, $node, $path );
    while ( ($id, $node ) = each( %{$self->{domainNodes}} ) ){
      $path = $self->getDomainsBackupPath( $id );
      Logging::debug( "Save domain dump $path" );
      XmlNode::setStartSavePath( $path );

      print $fh "<file>" . $path . "/" .$self->getDomainsBackupPath( $id, $fileName, 1 ) . ".xml" . "</file>\n";

      my $fileid = $self->finishChild( $node, $path, $self->getDomainsBackupPath( $id, $fileName, 1 ) );
      my $contentSize = $self->getContentSize($node);
      @{$infoxml->{$fileid}} = ( $contentSize, $self->getDomainObjectId( $id ), $node->getAttribute('guid') );
      $self->{storage}->createRepositoryIndex( $node->getAttribute('guid') . '_' . $self->getDomainObjectId( $id ) );

      #Keep total domain content size for each client
      my $clientId = PleskStructure::getClientIdForDomainId( $id );
      $usersContentSize->{$clientId} = (exists $usersContentSize->{$clientId}) ? $usersContentSize->{$clientId} + $contentSize : $contentSize;
    }
  }
}

sub finishClients {
  my ($self, $fileName, $fh, $selectResellers, $usersContentSize, $infoxml) = @_;

  if( exists $self->{clientNodes} ){
    my ( $id, $node, $path );
    while( ($id, $node ) = each( %{$self->{clientNodes}} ) ){
      my $fname;
      if (exists $self->{resellersNodes}->{$id}) {
        next if !$selectResellers;

        $path = $self->getResellersBackupPath( $id );
        $fname = $self->getResellersBackupPath( $id, $fileName, 1 );
        Logging::debug( "Save reseller dump $path" );
      } else {
        next if $selectResellers;

        $path = $self->getClientsBackupPath( $id );
        $fname = $self->getClientsBackupPath( $id, $fileName, 1 );
        Logging::debug( "Save client dump $path" );
      }

      XmlNode::setStartSavePath( $path );

      print $fh "<file>" . $path . "/" . $fname. ".xml" . "</file>\n";

      # Calculate current client size
      $usersContentSize->{$id} = (exists $usersContentSize->{$id})
        ? $usersContentSize->{$id} + $self->getContentSize($node)
        : $self->getContentSize($node);

      # Add current client size to the parent (reseller or admin) total content size
      my $parentId = PleskStructure::getClientParentId(PleskStructure::getClientNameFromId($id));
      $usersContentSize->{$parentId} = (exists $usersContentSize->{$parentId})
        ? $usersContentSize->{$parentId} + $usersContentSize->{$id}
        : $usersContentSize->{$id};

      my $fileid = $self->finishChild( $node, $path, $fname );
      @{$infoxml->{$fileid}} = ( $usersContentSize->{$id}, $self->getClientObjectId( $id ), $node->getAttribute('guid') );
      $self->{storage}->createRepositoryIndex( $node->getAttribute('guid') . '_' . $self->getClientObjectId( $id ) );
    }
  }
}

sub finishCustomers {
  my ($self, $fileName, $fh, $usersContentSize, $infoxml) = @_;
  $self->finishClients($fileName, $fh, 0, $usersContentSize, $infoxml);
}

sub finishResellers {
  my ($self, $fileName, $fh, $usersContentSize, $infoxml) = @_;
  $self->finishClients($fileName, $fh, 1, $usersContentSize, $infoxml);
}

sub finish {
    my ($self, $decryptPasswords) = @_;

    my %infoxml;

    my $ret = 0;
    my $fileName;
    my $mainDumpXmlFile;

    if( $self->{migrationMode} || scalar( @{$self->{roots}} )!=1 ){

        XmlNode::setStartSavePath( '' );

        if( $self->{listingOnlyMode} ) {
          $fileName = 'dump';
        } else {
          $fileName = $self->{fnamecreator}->getFileName( '', $self->{backupname}, '', 'info' );
        }

        $ret = $self->{storage}->finish( $self->_getRootForStorageFinish(''), '', $fileName );

        die "Cannot create main dump file!" if $ret!=0;

        if ($self->{listingOnlyMode} and $decryptPasswords) {
          $self->_decryptDump($self->{storage}->getFullOutputPath() . "/" . $fileName . ".xml");
        }

        return $ret if $self->{listingOnlyMode};

        $mainDumpXmlFile = $self->{storage}->getMainDumpXmlRelativePath();

        @{$infoxml{$mainDumpXmlFile}} = ( $self->getContentSize( $self->{root} ), $self->getAdminObjectId(), ( exists $self->{admin} ? $self->{admin}->getAttribute('guid') : $self->{ownerGuid} ) );
        $self->{storage}->createRepositoryIndex( ( exists $self->{admin} ? $self->{admin}->getAttribute('guid') : '' ) . '_' . $self->getAdminObjectId() );
    }
    $fileName = 'info';

    my ($fh, $targetFile);
    ($fh, $targetFile ) = File::Temp::tempfile( AgentConfig::getBackupTmpDir() . "/xmlFilesListXXXXXX", UNLINK => 1 );

    print $fh "<dump-files source-directory=\"".$self->{storage}->getFullOutputPath()."\">\n";
    print $fh "<file>" . $self->{storage}->getMainDumpXmlFile() . "</file>\n" if $self->{storage}->getMainDumpXmlFile();

    my %usersContentSize; # Summary size of each customer, reseller and admin content

    $self->finishDomains($fileName, $fh, \%usersContentSize, \%infoxml);
    $self->finishCustomers($fileName, $fh, \%usersContentSize, \%infoxml);
    $self->finishResellers($fileName, $fh, \%usersContentSize, \%infoxml);

    if (exists $infoxml{$mainDumpXmlFile}) {
      my $adminId = PleskStructure::getAdminId();
      @{$infoxml{$mainDumpXmlFile}}[0] += $usersContentSize{$adminId} if exists $usersContentSize{$adminId};
    }

    print $fh "</dump-files>";
    close $fh;

    # Disable built-in password decryption
    #if ($self->{decrypt_full_dump}) {
    #  $self->_decryptDump($targetFile);
    #} elsif (!$self->{migrationMode}) {
    #    $self->_encryptDump($targetFile);
    #}

    my $path;

    #Create discovered files
    foreach my $fileid(  keys( %infoxml ) ){
       my $idx = rindex( $fileid, '/' );
       $path = $fileid;
       $path = substr( $path, 0, $idx ) if $idx>0;
       my $xmlsize = 0;
       my $files;
       foreach my $id( keys( %infoxml ) ){
         if( $idx<0 || index( $id, $path )==0 ){
            $files = $self->{storage}->getFilesFromId( $id );
            foreach my $filedata( @{$files} ) {
              $xmlsize += $filedata->[1];
            }
         }
       }
       $files = $self->{storage}->getFilesFromId( $fileid );
       if( scalar( @{$files} )==1 ){
          my $xmlfiledata = $files->[0];
          $xmlsize += @{$infoxml{$fileid}}[0];
          my $objId = @{$infoxml{$fileid}}[1];
          my $objGuid = @{$infoxml{$fileid}}[2];
          $self->{storage}->writeDiscovered( $self->{storage}->getFilePathFromId( $fileid ), $xmlfiledata->[0], $xmlsize, $self->{ownerGuid}, $self->{ownerType}, $objGuid, $objId );
       }
    }

    return 0;
}

sub _decryptDump { # XXX another definition at line 265
  my ($self, $targetFile) = @_;

  eval {
    my $encryptCmd = AgentConfig::getEncryptUtil();
    push(@{$encryptCmd}, '--decrypt-by-plesk', '-backup-files-map', $targetFile);
    Logging::debug("Execute: @{$encryptCmd}");
    my $stderr;
    IPC::Run::run($encryptCmd, '2>', \$stderr) or die($stderr);
  };
  if ($@) {
    Logging::error("Cannot decrypt dump file (this is not fatal error!): $@", 'UtilityError');
  }
}

sub _encryptDump {
  my ($self, $targetFile) = @_;
  
  my $password = $ENV{'PLESK_BACKUP_CRYPT_KEY'};
  delete $ENV{'PLESK_BACKUP_CRYPT_KEY'} if ($password);

  eval {
    my $encryptCmd = AgentConfig::getEncryptUtil();
    push(@{$encryptCmd}, '--encrypt-by-plesk', '-backup-files-map', $targetFile);
    Logging::debug("Execute: @{$encryptCmd}");
    my $stderr;
    IPC::Run::run($encryptCmd, '2>', \$stderr) or die($stderr);
  };
  if ($@) {
    Logging::error("Cannot encrypt dump file (this is not fatal error!): $@", 'UtilityError');
  }
    
  $ENV{'PLESK_BACKUP_CRYPT_KEY'} = $password if ($password);
}

sub getContentSize{
  my ($self, $node) = @_;
  my $size = 0;

  foreach my $child ( $node->getChildren() ) {
    if( $child ) {
      if( $child->getName() eq 'cid' ){
        if( !defined($child->getAttribute('referrer')) ) {
          foreach my $cid ( $child->getChildren() ){
            $size += $cid->getAttribute('size');
          }
        }
      }
      else {
        $size += $self->getContentSize($child);
      }
    }
  }
  return $size;
}

### Server functions ###

sub setServerSettings {
  my ( $self ) = @_;
  my $serverNode = XmlNode->new('server');
  $self->{serverNode} = $serverNode;
}

sub addServerNodeToDump {
  my ( $self ) = @_;

  my $root = $self->{root};
  unless (defined $root)
  {
    Logging::warning("Root node is not set", 'PackerStructure');
    return;
  }

  if ( ref ($self->{serverNode}) =~ /XmlNode/ ) {
    $root->addChild($self->{serverNode});
  }
}

sub setServerColdFusion {
  my ($self, $params) = @_;

  my $serverNode = $self->{serverNode};

  my $path = $params->{'cf_instance_path'};

  return unless defined $path;

  my $type = ( $params->{'cf_instance_type'} eq 'j2ee' ) ? 'j2ee' : 'server';

  my $coldfusionNode = XmlNode->new('coldfusion');
  $coldfusionNode->setAttribute( 'instance-type', $type);
  $coldfusionNode->setAttribute( 'instance-path', $path);

  $serverNode->addChild($coldfusionNode);
}

sub addPanelCertificate {
  my ($self) = @_;
  my $serverNode = $self->{serverNode};

  my $path = AgentConfig::get('PRODUCT_ROOT_D') . "/admin/conf";
  my $httpsdFilename = 'httpsd.pem';

  if ( -e "$path/$httpsdFilename") {
    my $certNode = XmlNode->new('panel-certificate');

    my $content = undef;

    open HTTPSDFILE, $path . "/" . $httpsdFilename;
    binmode(HTTPSDFILE);
    while (<HTTPSDFILE>) {
      $content .= $_;
    }
    close HTTPSDFILE;

    $certNode->addChild(XmlNode->new('cp-certificate', 'content' => $self->{base64}->{'ENCODE'}->($content) ));

    my $rootchainFilename = 'rootchain.pem';
    if ( -e "$path/$rootchainFilename") {
      my $content = undef;
      open ROOTCHAINFILE, $path . "/" . $rootchainFilename;
      binmode(ROOTCHAINFILE);
      while (<ROOTCHAINFILE>) {
        $content .= $_;
      }
      close ROOTCHAINFILE;
      $certNode->addChild(XmlNode->new('cp-rootchain', 'content' => $self->{base64}->{'ENCODE'}->($content) ));
    }

    $serverNode->getChild( 'certificates', 1 )->addChild($certNode);
  }
}

sub addServerSiteIsolationConfig {
  my ($self) = @_;
  my $root = $self->{serverNode};

  my $path = AgentConfig::get('PRODUCT_ROOT_D') . "/admin/conf";
  my $file = 'site_isolation_settings.ini';

  if ( -e "$path/$file") {

    my $siteIsolationNode = XmlNode->new('site-isolation');

    my $configText = undef;

    open CONFIGFILE, $path . "/" . $file;
    binmode(CONFIGFILE);

    while (<CONFIGFILE>) {
      $configText .= $_;
    }

    close CONFIGFILE;

    $siteIsolationNode->addChild(XmlNode->new('config', 'content' => $self->{base64}->{'ENCODE'}->($configText) ));
    $root->addChild($siteIsolationNode);
  }
}

sub setServerEventHandler {
  my ($self, $ptrEvents, $ptrActions, $ptrParams) = @_;

  my $root = $self->{serverNode};
  my %events = %{$ptrEvents};
  my @actions = @{$ptrActions};
  my %params = %{$ptrParams};

  my $eventsNode = XmlNode->new('events');

  my $rotationNode = XmlNode->new('rotation');
  if ($params{'actionlog_rot_type'} eq 'forever') {
    $rotationNode->addChild(XmlNode->new('disabled'));
  }elsif($params{'actionlog_rot_type'} eq 'by_period') {
    my $byTimeNode = XmlNode->new('by-time');
    $byTimeNode->setAttribute('period-type', $params{'actionlog_rot_period'});
    $byTimeNode->setAttribute('period', $params{'actionlog_rot_num_periods'} );
    $rotationNode->addChild($byTimeNode);
  }elsif($params{'actionlog_rot_type'} eq 'by_number') {
    my $byNumberNode = XmlNode->new('by-entires');
    $byNumberNode->setAttribute('count', $params{'actionlog_rot_num_records'});
    $rotationNode->addChild($byNumberNode);
  }

  $eventsNode->addChild($rotationNode);

  foreach my $action (@actions) {
    my $eventNode = XmlNode->new('event');
    $eventNode->setAttribute('enabled', $action->{'enabled'});
    $eventNode->setAttribute('description', $action->{'descr'});
    $eventNode->setAttribute('name', $action->{'name'});
    if (exists $events{$action->{'id'}}) {
      foreach my $handler (@{$events{$action->{'id'}}}) {
        my $handlerNode = XmlNode->new('handler');
        $handlerNode->setAttribute('command', $handler->{'command'});
        $handlerNode->setAttribute('user', $handler->{'user'});
        $handlerNode->setAttribute('priority', $handler->{'priority'});
        $eventNode->addChild($handlerNode);
      }
    }
    $eventsNode->addChild($eventNode);
  }

  $root->addChild($eventsNode);
}

sub addServerNotifications {
  my ( $self, $expirationWarnDays, $ptrNotifications, $ptrNotes ) = @_;
  my $root = $self->{serverNode};
  my @notifications = @{$ptrNotifications};
  my %notes = %{$ptrNotes};

  my $notificationsNode = XmlNode->new('notifications');
  $notificationsNode->setAttribute( 'expiration-warning-days', $expirationWarnDays);
  foreach my $notification (@notifications) {
    my $notificationNode = XmlNode->new( 'notification' );
    $notificationNode->setAttribute( 'id',            $notification->{'id'} );
    $notificationNode->setAttribute( 'send2admin',    $notification->{'send2admin'}    ? 'true' : 'false' );
    $notificationNode->setAttribute( 'send2reseller', $notification->{'send2reseller'} ? 'true' : 'false' );
    $notificationNode->setAttribute( 'send2client',   $notification->{'send2client'}   ? 'true' : 'false' );
    $notificationNode->setAttribute( 'send2email',    $notification->{'send2email'}    ? 'true' : 'false' );

    $notificationNode->setAttribute( 'email', $notification->{'email'} ) if defined $notification->{'email'};
    $notificationNode->setAttribute( 'subj',  $notification->{'subj'} ) if defined $notification->{'subj'};

    my $noteText = (exists $notes{$notification->{'note_id'}}) ? $notes{$notification->{'note_id'}} : '';
    $notificationNode->addChild( XmlNode->new( 'notice-text', 'content' => $noteText ) );
    
    $notificationsNode->addChild( $notificationNode );
  }

  $root->addChild($notificationsNode);
}

sub addServerCustomButton {
  my ( $self, $id, $optionsPtr, $customButtonsDir, $icon ) = @_;

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump custom buttons settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $node = $self->makeCustomButtonNode( 'admin', undef, $id, $optionsPtr, $customButtonsDir, $icon );

  $parent->getChild( 'interface-preferences', 1 )->addChild($node);
}

my %dumpedSystemIps;

sub addServerIp {
  my ( $self, $ip ) = @_;

  $self->setServerSettings() unless defined $self->{serverNode};

  if ( ! $dumpedSystemIps{$ip->{'ip_address'}} ) {
    $self->makeSystemIpNode( $self->{serverNode}, $ip );
    $dumpedSystemIps{$ip->{'ip_address'}} = 1;
  }
}

sub setServerDefaultIp {
  my ( $self, $ip ) = @_;

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server default IP, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $parent->getChild( 'properties', 1 )->addChild( XmlNode->new( 'default-ip', 'content' => $ip ) );
}

sub setServerHostname {
  my ( $self, $hostname ) = @_;

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server host name, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $parent->getChild( 'properties', 1 )->addChild( XmlNode->new( 'hostname', 'content' => $hostname ) );
}

sub setServerAdminInfo {
  my ( $self, $ptrAdmin, $passwd, $max_btn_len, $send_announce, $external_id, $cron, $descriptions ) = @_;

  my $parent = $self->{admin};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server admin user information, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $parent->setAttribute( 'max-button-length', $max_btn_len ) if $max_btn_len;
  $parent->setAttribute( 'send-announce', $send_announce ) if $send_announce;
  $parent->setAttribute( 'external-id', $external_id ) if defined $external_id and $external_id ne '';

  my %adminInfo = (
    'cname' => 'company',
    'phone'   => 'phone',
    'fax'     => 'fax',
    'address' => 'address',
    'city'    => 'city',
    'state'   => 'state',
    'pcode'   => 'zip',
    'country' => 'country',
    'email'   => 'email',
    'pname'   => 'name',
    'locale'  => 'locale',
  );

  my $pref = $parent->getChild( 'preferences', 1 );
  while ( my ( $name, $value ) = each( %{$ptrAdmin} ) ) {

    #Attributes transformation. Bug 97408
    $name =~ s/^admin_//g;
    next if not defined $adminInfo{$name};
    $pref->addChild( XmlNode->new( 'pinfo', 'attributes' => { 'name' => $adminInfo{$name} }, 'content'    => $value ) );
  }

  if (defined $cron) {
    if (PleskVersion::atLeast(12, 1, 12)) {
      eval {require XML::Simple; 1;};
      my $xs = XML::Simple->new(ForceArray => 1);
      my $scheduledTasksXml = $xs->XMLout($cron, RootName => 'scheduled-tasks');
      $scheduledTasksXml = Encode::encode('UTF-8', $scheduledTasksXml); # workaround for UTF8 symbols
      $pref->addChild(XmlNode->new(undef, 'raw' => $scheduledTasksXml));
    } else {
      $pref->addChild( XmlNode->new( 'cron', 'content' => $cron ) );
    }
  }

  $self->makeDescriptionsNode( $pref, $descriptions );
}

sub addServerDb {
  my ( $self, $dbServerPtr, $passwd, $default ) = @_;

  my %dbServer = %{$dbServerPtr};

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server database, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $dbServerNode = $self->makeDbServerNodeWithoutCredentials(\%dbServer);

  if ( $default ) {
    $dbServerNode->setAttribute( 'default', 'true' );
  }

  if ( ($dbServer{'admin_login'}) and ($dbServer{'admin_login'} ne '')) {
    my $adminNode =
      XmlNode->new( 'db-admin',
      "attributes" => { "name" => "$dbServer{'admin_login'}" } );
    my $password = PleskStructure::getDbServerAdminPassword($dbServerPtr);
    my $passwordNode =
      CommonPacker::makePasswordNode( $password, 'plain' )
      ;    # password type for dbservers is always plain
    $adminNode->addChild($passwordNode);

    $dbServerNode->addChild($adminNode);
  }

  $parent->getChild( 'db-servers', 1 )->addChild($dbServerNode);
}

sub makeDbServerNodeWithoutCredentials {
  my ($self, $dbServer) = @_;

  my $dbServerNode = XmlNode->new('db-server');

  $dbServerNode->setAttribute( 'type', $dbServer->{'type'} );

  $dbServerNode->addChild( XmlNode->new( 'host', 'content' => "$dbServer->{'host'}" ) );
  $dbServerNode->addChild( XmlNode->new( 'port', 'content' => "$dbServer->{'port'}" ) );

  return $dbServerNode;
}

sub makeDatabaseUserRemoteAccessRulesNode {
    my ($self, $dbUser, $dbId) = @_;

    Logging::debug("Backing up remote access rules for db user " . $dbUser->{'login'});
    my @rules = @{DAL::DatabaseUserRemoteAccessRules($dbUser->{'login'}, $dbId)};
    if ( @rules ) {
        my $rulesNode = XmlNode->new('remote-access-rules');
        for my $ptrRow ( @rules ) {
            my $ruleItemNode = XmlNode->new( 'rule-item' );
            $ruleItemNode->setAttribute( 'type', $ptrRow->[0] );
            $ruleItemNode->setAttribute( 'ip-address', $ptrRow->[1] );
            $ruleItemNode->setAttribute( 'ip-subnet-mask', $ptrRow->[2] );
            $rulesNode->addChild( $ruleItemNode );
        }

        return $rulesNode;
    }

    return;
}

sub addServerKey {
  my ( $self, $keyId, $keyName, $keyDir, $additional, $instance ) = @_;

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server license key, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  if ( -e "$keyDir/$keyName" ) {

    my $keyNode =
      XmlNode->new( 'key',
      'attributes' => { 'additional' => $additional } );

    $keyNode->setAttribute('instance-id', $instance) if $instance ne '';

    my $cid = $self->{content_transport}->addAdminContent(
      'key',
      undef,
      "$keyId.key",
      "directory" => $keyDir,
      "include"   => [$keyName]
    );
    $keyNode->getChild( 'content', 1, 1 )->addChild($cid) if $cid;

    $parent->getChild( 'keys', 1 )->addChild($keyNode);
  }

}

sub setServerMail {
  my ( $self, $letterSize, $paramsPtr, $mailSettingsPtr, $blackListPtr, $whiteListPtr, $ipAddressesPtr, $externalWebmailsPtr ) = @_;

  my %params = %{$paramsPtr};
  my %mailSettings = %{$mailSettingsPtr};
  my @blackList = @{$blackListPtr};
  my %whiteList = %{$whiteListPtr};
  my @externalWebmails = @{$externalWebmailsPtr};

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server mail settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $mailNode = XmlNode->new(
    'mail-settings',
    'attributes' => {
      'relay'           => $mailSettings{'relay'},
      'use-vocabulary'  => (defined($params{'use_vocabulary'}) and ($params{'use_vocabulary'} eq 'true'))
        ? 'true'
        : 'false',
      'short-pop3-names' => (defined($params{'allow_short_pop3_names'}) and ($params{'allow_short_pop3_names'})) eq 'enabled'
        ? 'true'
        : 'false',
      'message-submission' => (defined($mailSettings{'message_submission'}) and ($mailSettings{'message_submission'} eq 'true'))
        ? 'true'
        : 'false',
      'sign-outgoing-mail' => (defined($mailSettings{'domain_keys_sign'}) and ($mailSettings{'domain_keys_sign'} eq 'true'))
        ? 'true'
        : 'false',
      'verify-incoming-mail' => (defined($mailSettings{'domain_keys_verify'}) and ($mailSettings{'domain_keys_verify'} eq 'true'))
        ? 'true'
        : 'false',
    }
  );

  if (defined $letterSize) {
    $mailNode->setAttribute('max-letter-size', $letterSize);
  }
  if ( PleskVersion::atLeast( 11, 6, 5 ) ) {
    if (defined $mailSettings{'courier_max_connections'}) {
      $mailNode->setAttribute('courier-max-connections', $mailSettings{'courier_max_connections'});
    }
    if (defined $mailSettings{'courier_max_connections_per_ip'}) {
      $mailNode->setAttribute('courier-max-connections-per-ip', $mailSettings{'courier_max_connections_per_ip'});
    }
    if (defined $mailSettings{'dovecot_max_connections'}) {
      $mailNode->setAttribute('dovecot-max-connections', $mailSettings{'dovecot_max_connections'});
    }
    if (defined $mailSettings{'dovecot_max_connections_per_ip'}) {
      $mailNode->setAttribute('dovecot-max-connections-per-ip', $mailSettings{'dovecot_max_connections_per_ip'});
    }
  }

  my $spfNode = XmlNode->new(
    'spf',
    'attributes' => {
      'status' => (exists $mailSettings{'spf_enabled'} and $mailSettings{'spf_enabled'} eq 'true') ? 'true' : 'false'
    }
  );
  $spfNode->setAttribute('spf-behavior', (exists $mailSettings{'spf_behavior'} and "$mailSettings{'spf_behavior'}" ne '' ) ? $mailSettings{'spf_behavior'} : 1);
  if (exists $mailSettings{'spf_dnserrignore'}) {
    $spfNode->setAttribute('spf-ignore-dns-error', $mailSettings{'spf_dnserrignore'} eq 'true' ? 'true' : 'false');
  }

  $spfNode->addChild( XmlNode->new( 'spf-rules', 'content' => exists $mailSettings{'spf_rules'} ? $mailSettings{'spf_rules'} : '' ) );
  $spfNode->addChild( XmlNode->new( 'spf-guess', 'content' => exists $mailSettings{'spf_guess'} ? $mailSettings{'spf_guess'} : '' ) );
  $spfNode->addChild( XmlNode->new( 'spf-exp', 'content' => exists $mailSettings{'spf_exp'} ? $mailSettings{'spf_exp'} : '' ) );

  $mailNode->addChild($spfNode);

  my $rblNode = XmlNode->new(
    'rbl',
    'attributes' => {
        'status' => ( exists $params{'rbl'} and $params{'rbl'} eq 'true' )
      ? 'true'
      : 'false'
    }
  );
  $rblNode->addChild( XmlNode->new( 'rbl-server', 'content' => exists $mailSettings{'rbl_server'} ? $mailSettings{'rbl_server'} : '' ) );

  $mailNode->addChild($rblNode);

  if ( $mailSettings{'relay'} eq 'auth' ) {
    $mailNode->setAttribute( 'pop-auth', my $disable_pop_auth = (exists $mailSettings{'disable_pop_auth'} and $mailSettings{'disable_pop_auth'} ? 'false' : 'true') );
    $mailNode->setAttribute( 'smtp-auth', (exists $mailSettings{'disable_smtp_auth'} and $mailSettings{'disable_smtp_auth'} ? 'false' : 'true') );
    $mailNode->setAttribute( 'poplock-time', $mailSettings{'poplock_time'} )  if ('false' == $disable_pop_auth);
  }

  # black list
  my $listNode = XmlNode->new('black-list');

  for my $listItem (@blackList) {
    $listNode->addChild( XmlNode->new( 'list-item', 'content' => $listItem ) );
  }
  $mailNode->addChild($listNode);

  # white list
  $listNode = XmlNode->new('white-list');

  for my $key (keys %whiteList) {
    $listNode->addChild(
      XmlNode->new(
        'list-item', 'content' => $key . '/' . $whiteList{$key}
      )
    );
  }
  $mailNode->addChild($listNode);

  $parent->addChild($mailNode);
  
  if (@externalWebmails) {
    $self->_makeExternalWebmails(\@externalWebmails);
  }

  for my $key (keys %mailSettings) {
    if ($key =~ /^outgoing_messages_/) {
      $self->_makeServerOutgoingMessagesParameter($key, $mailSettings{$key});
    }
  }

  $self->_makeSeverOutgoingEmailMode($mailSettingsPtr, $ipAddressesPtr);
}

sub setServerDNS {
  my ( $self, $paramsPtr, $recordsPtr ) = @_;

  my @records = @{$recordsPtr};

  my %params = %{$paramsPtr};

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server DNS settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  # dump dns
  my $dnsNode = XmlNode->new(
    'dns-settings',
    'attributes' => {
      'recursion' => defined $params{'dns_recursion'}
      ? $params{'dns_recursion'}
      : 'any'
    }
  );

  my $dnsZone = XmlNode->new(
    'dns-zone',
    'attributes' =>
      { 'email' => 'root@localhost.localdomain', 'type' => 'master' },
    'children' =>
      [ Status::make( (not exists $params{'dns_zone_status'} or $params{'dns_zone_status'} ne 'false') ? 0 : 16 ) ]
  );

  $dnsZone->setAttribute( 'serial-format', $params{'soa_serial_format'} ) if exists $params{'soa_serial_format'};

  my %zone_params = (
    'ttl'     => 1 * 86400,
    'refresh' => 3 * 3600,
    'retry'   => 1 * 3600,
    'expire'  => 7 * 86400,
    'minimum' => 3 * 3600
  );

  my %zone_units = (
    'ttl'     => 86400,
    'refresh' => 3600,
    'retry'   => 3600,
    'expire'  => 86400,
    'minimum' => 3600
  );

  foreach my $zone_param ( keys %zone_params ) {
    my $soa_param = 'soa_' . $zone_param;

    $dnsZone->addChild(
      $self->makeDnsZoneParam(
        $zone_param,
        exists $params{ $soa_param . '_unit' }
        ? $params{ $soa_param . '_unit' }
        : $zone_units{$zone_param},
        exists $params{$soa_param} ? $params{$soa_param}
        : $zone_params{$zone_param},
      )
    );
  }

  # dns records
  for my $ptrHash (@records) {
    my $dnsrec = $self->makeDnsRecord($ptrHash);
    if ($dnsrec) {
      $dnsZone->addChild($dnsrec);
    }
  }

  $dnsNode->addChild($dnsZone);

  # dump common acl for dns zone

  my $aclNode = XmlNode->new('common-acl');

  foreach my $param ( keys %params ) {
    if ( $param =~ /^DNS_Allow_Transfer/ ) {
      $aclNode->addChild( XmlNode->new( 'list-item', 'content' => $params{$param} ) );
    }
  }

  $dnsNode->addChild($aclNode);

  my $subdomainOwnZone = 'false';
  if (exists($params{'subdomain_own_zones'})) {
    $subdomainOwnZone =  $params{'subdomain_own_zones'} eq 'false' ? 'false' : 'true';
  }else {
    $subdomainOwnZone = 'false';
  }

  $dnsNode->addChild( XmlNode->new( 'subdomain-own-zones', 'content' => $subdomainOwnZone ) );

  $parent->addChild($dnsNode);
}

sub setServerWebSettings {
  my ($self, $embeddedInfo) = @_;

  if ('' ne $embeddedInfo && defined($embeddedInfo->{'web-settings'})) {
    my ($webSettings) = @{$embeddedInfo->{'web-settings'}};
    eval {require XML::Simple; 1;};
    my $xs = XML::Simple->new(ForceArray => 1);
    my $webSettingsXml = $xs->XMLout($webSettings, RootName => 'web-settings');
    $webSettingsXml = Encode::encode('UTF-8', $webSettingsXml); # workaround for UTF8 symbols
    $self->{serverNode}->addChild(XmlNode->new(undef, 'raw' => $webSettingsXml));
  }
}

sub addServerCertificate {
  my ( $self, $name, $cert, $csr, $ca_cert, $pvt_key, $default) = @_;

  my $parent = $self->{serverNode};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump server certificate, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $root = XmlNode->new('certificate');

  addUrlDecodedTextNode( $root, 'certificate-data', $cert ) if defined($cert);
  addUrlDecodedTextNode( $root, 'signing-request',  $csr ) if defined($csr);
  addUrlDecodedTextNode( $root, 'ca-certificate',   $ca_cert ) if defined($ca_cert);
  addUrlDecodedTextNode( $root, 'private-key', $pvt_key ) if defined($pvt_key);
  $root->setAttribute( 'name', $name );

  if ($default) {
    $root->setAttribute('default', 'true');
  }

  $parent->getChild( 'certificates', 1 )->addChild($root);
}

sub addTemplateToServer {
  my ( $self, $templateType, $templateName, $dataPtr ) = @_;
  my %templates = (
    'reseller' => 'reseller-template',
    'domain' => 'domain-template',
    'domain_addon' => 'domain-template'
  );

  unless ( exists( $templates{$templateType} ) ){
    return;
  }

  $self->setServerSettings() unless defined $self->{serverNode};

  my $root = $self->{serverNode};
  my $node = $self->makeTemplateNode($templates{$templateType}, $templateName, $dataPtr);

  $root->getChild( 'account-templates', 1 )->addChild($node);

}

sub makePlanItemNode {
  my ( $self, $planItemPtr, $planItemPropsPtr, $customButtonsDir ) = @_;
  return unless ( ref($planItemPtr) =~ /HASH/ );
  return unless ( ref($planItemPropsPtr) =~ /HASH/ );

  return unless ( exists( $planItemPtr->{'name'}) && exists( $planItemPtr->{'classname'}) && exists( $planItemPtr->{'uuid'}) );

  my $planItemNode = XmlNode->new('plan-item');
  $planItemNode->setAttribute( 'name',    $planItemPtr->{'name'} );
  $planItemNode->setAttribute( 'type',    $planItemPtr->{'classname'} );
  $planItemNode->setAttribute( 'guid',    $planItemPtr->{'uuid'} );
  $planItemNode->setAttribute( 'visible', ($planItemPtr->{'isVisible'})? 'true' : 'false' );

  if ( ( $planItemPtr->{'classname'} eq 'Plan_Item_Custom') && ( defined $planItemPropsPtr->{'file'} ) ) {
    my $file = $planItemPropsPtr->{'file'};
    my $filename = "$customButtonsDir/$file";
    if( -f $filename) {
      my $cid = $self->{content_transport}->addAdminContent('icon', undef, 'icon_planitem'.$planItemPtr->{'id'}, 'directory' => $customButtonsDir, 'include' => [$file]);
      $planItemNode->getChild('content', 1, 1)->addChild($cid) if $cid;
    }
  }
  my $applicableNode = XmlNode->new( 'applicable' );
  $applicableNode->addChild( XmlNode->new( 'applicable-to-subscription' ) ) if ( $planItemPtr->{'applicableToSubscription'} == 1 );
  $applicableNode->addChild( XmlNode->new( 'applicable-to-site'         ) ) if ( $planItemPtr->{'applicableToSite'        } == 1 );
  $applicableNode->addChild( XmlNode->new( 'applicable-to-email'        ) ) if ( $planItemPtr->{'applicableToEmail'       } == 1 );
  $planItemNode->addChild( $applicableNode );

  my $propertiesNode = XmlNode->new( 'properties' );
  while ( my ($name,$value) = each( %{$planItemPropsPtr} ) ) {
    next if ($name eq '');
    my $planItemPropertyNode = XmlNode->new( 'plan-item-property' );
    $planItemPropertyNode->setAttribute( 'name', $name);
    if ( $value ne '' ) {
      $planItemPropertyNode->setText( $value );
    }
    $propertiesNode->addChild( $planItemPropertyNode );
  }
  $planItemNode->addChild( $propertiesNode );

  return $planItemNode;
}

sub addPlanItemToServer {
  my ( $self, $planItemPtr, $planItemPropsPtr, $customButtonsDir ) = @_;
  my $root = $self->{serverNode};

  my $node = $self->makePlanItemNode( $planItemPtr, $planItemPropsPtr, $customButtonsDir );

  $root->getChild( 'account-templates', 1 )->addChild($node) if defined $node;
}

sub setServerSSO {
  my ( $self, $paramsPtr, $cert, $idpCert ) = @_;

  my %params = %{$paramsPtr};

  my $root = $self->{serverNode};

  my $ownership = defined $params{'admin_ownership'}
    && $params{'admin_ownership'} eq 'true' ? 'true' : 'false';
  my $enabled = defined $params{'sso_enabled'}
    && $params{'sso_enabled'} eq 'true' ? 'true' : 'false';

  my $ssoNode = XmlNode->new( 'sso-settings',
    'attributes' =>  { 'enabled' => $enabled }  );

  my @settings = (
    'sso_server',         'sso_relay',
    'sso_application_id', 'sso_idp_api_version',
    'sso_idp_id'
  );

  my %settings_map = (
    'sso_server'          => 'sso-server',
    'sso_relay'           => 'sso-relay',
    'sso_application_id'  => 'sso-application-id',
    'sso_idp_api_version' => 'sso-idp-api-version',
    'sso_idp_id'          => 'sso-idp-id'
  );

  foreach my $setting (@settings) {
    $ssoNode->addChild(
      XmlNode->new(
        $settings_map{$setting}, 'content' => $params{$setting}
      )
    ) if ( defined $params{$setting} );
  }

  if ( $cert )
  {
    $ssoNode->addChild( XmlNode->new( 'sso-certificate', 'content' => $cert ) );
  }

  if ( $idpCert ) {
    $ssoNode->addChild(
      XmlNode->new( 'sso-idp-cert', 'content' => $idpCert ) );
  }

  $root->addChild($ssoNode);
}

sub setServerSSOBranding {
  my ( $self, $brandingPtr ) = @_;

  my @branding = @{$brandingPtr};

  my $root = $self->{serverNode};

  my @ssoNodes = $root->getChildren('sso-settings');
  if ( scalar(@ssoNodes) < 1 ) {
    Logging::warning("Unable to dump SSO branding settings, because of dump XML structure is not full", 'PackerStructure');
    return;
  }
  my $ssoNode = $ssoNodes[0];

  my $ssoBrandingNode = XmlNode->new('sso-branding');
  if ( @branding ) {
    for my $ptrRow (@branding) {
      my $recordNode = XmlNode->new(
        'record',
        'children' => [
          XmlNode->new( 'idp-url', 'content' => $ptrRow->[1] ),
          XmlNode->new(
            'http-request-domain', 'content' => $ptrRow->[0]
          )
        ]
      );
      $ssoBrandingNode->addChild($recordNode);
    }
    $ssoNode->addChild($ssoBrandingNode);
  }
}

sub setServerAppVault {
  my ( $self ) = @_;

  my $root = $self->{serverNode};
  return $root->getChild('application-vault', 1);
}

sub setServerPackagesPool {
  my ( $self ) = @_;

  my $appVaultNode = $self->setServerAppVault();
  return $appVaultNode->getChild('sapp-packages-pool', 1);
}

my %dumpedApplications;

sub addServerAppPackage {
  my ($self, $name, $version, $release, $distrib_path, $file_name, $is_uploaded, $is_visible, $applicationPackage) = @_;

  my $fullname = $name."-".$version."-".$release;
  if (exists $dumpedApplications{$fullname}) {
        return;
  }

  $dumpedApplications{$fullname} = 1;

  my $appPackagesPoolNode = $self->setServerPackagesPool();

  my $packageNode = XmlNode->new('sapp-package');
  if ( defined $applicationPackage and $applicationPackage->getSappPackageId() ) {
      $packageNode->addChild(  XmlNode->new( 'sapp-package-id', 'content' => $applicationPackage->getSappPackageId() ) );
  }
  $packageNode->addChild(  XmlNode->new( 'sapp-name', 'content' => $name ) );
  $packageNode->addChild(  XmlNode->new( 'sapp-version', 'content' => $version ) )  if ( $version );
  $packageNode->addChild( XmlNode->new( 'sapp-release', 'content' => $release ) )  if ( $release );
  $packageNode->addChild( XmlNode->new( 'sapp-uploaded' ))  if ( defined $is_uploaded && $is_uploaded ne "0" );
  $packageNode->addChild( XmlNode->new( 'sapp-visible' ))  if ( defined $is_visible && $is_visible ne "0" );


  if( $distrib_path && $file_name )
  {
    my $real_path = $distrib_path . "/" . $file_name;

    if ( -e $real_path ) {
      my $cid = $self->{content_transport}->addAdminContent(
        'sapp-distrib',
        undef,
        "sapp-distrib." . HelpFuncs::generateProcessId(),
        "directory" => $distrib_path,
        "include"   => [$file_name]
      );
      $packageNode->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;
    }
    else { return; }

    if ( defined $applicationPackage ) {
      my $settings = $applicationPackage->getSettings();
      if ( $settings ) {
        my $settingsNode = XmlNode->new('sapp-settings');
        foreach my $setting ( keys %{$settings} ) {
            my $settingNode = XmlNode->new('setting');
            $settingNode->addChild( XmlNode->new( 'name', 'content' => $setting ) );
            if (ref($settings->{$setting}) =~ /ARRAY/) {
              foreach my $value ( @{$settings->{$setting}}) {
                $settingNode->addChild( XmlNode->new( 'value', 'content' => $value ) );
              }
            } else {
              $settingNode->addChild( XmlNode->new( 'value', 'content' => $settings->{$setting} ) );
            }
            $settingsNode->addChild($settingNode);
        }
        $packageNode->addChild($settingsNode);
      }
    }

    $appPackagesPoolNode->addChild($packageNode);
  }
}

sub setServerAppItemsPool {
  my ( $self ) = @_;

  my $appVaultNode = $self->setServerAppVault();
  return $appVaultNode->getChild('sapp-items-pool',1);
}

sub addServerAppItem {
  my ($self, $paramsPtr) = @_;

  my %params = %{$paramsPtr};

  my $appItemsPoolNode = $self->setServerAppItemsPool();

  my $appItemNode = XmlNode->new('sapp-item');
  $appItemNode->setAttribute( 'enabled',
    ( $params{'disabled'} eq 'true' ) ? 'false' : 'true' )
    if defined $params{'disabled'};
  my $appSpecNode = XmlNode->new('sapp-spec');
  $appSpecNode->addChild(
    XmlNode->new( 'sapp-name', 'content' => $params{'sapp_name'} ) );
  $appSpecNode->addChild(
    XmlNode->new(
      'sapp-version', 'content' => $params{'sapp_version'}
    )
  ) if defined $params{'sapp_version'};
  $appSpecNode->addChild(
    XmlNode->new(
      'sapp-release', 'content' => $params{'sapp_release'}
    )
  ) if defined $params{'sapp_release'};
  $appItemNode->addChild($appSpecNode);
  $appItemNode->addChild(
    XmlNode->new(
      'license-type',
      'content' => defined( $params{'license_type_id'} )
      ? $params{'license_type_id'}
      : "0"
    )
  );
  $appItemNode->addChild(
    XmlNode->new( 'shared', 'content' => $params{'shared'} ) )
    if defined $params{'shared'};
  $appItemNode->addChild(
    XmlNode->new( 'description', 'content' => $params{'description'} )
  ) if defined $params{'description'};
  $appItemNode->addChild(
    XmlNode->new(
      'instances-limit', 'content' => $params{'instances_limit'}
    )
  ) if defined $params{'instances_limit'};

  $appItemsPoolNode->addChild($appItemNode);
}

sub setServerAppLicensesPool {
  my ( $self ) = @_;

  my $appVaultNode = $self->setServerAppVault();
  return $appVaultNode->getChild('sapp-licenses-pool',1);
}

sub addServerAppLicense {
  my ($self, $keyNumber, $licenseType, $licenseText) = @_;

  my $licensePoolNode = $self->setServerAppLicensesPool();

  my $licenseNode = XmlNode->new(
    'sapp-license',
    'children' => [
      XmlNode->new( 'key-number',   'content' => $keyNumber ),
      XmlNode->new( 'license-type', 'content' => $licenseType ),
      XmlNode->new( 'license-text', 'content' => $licenseText )
    ]
  );

  $licensePoolNode->addChild($licenseNode);
}

sub setServerSBConfig {
  my ( $self, $configPtr ) = @_;

  my @config = @{$configPtr};

  my $serverNode = $self->{serverNode};

  my $sbNode = XmlNode->new('sb-config');
  for my $ptrHash (@config) {
    $sbNode->addChild(
      XmlNode->new(
        'sb-param',
        'children' => [
          XmlNode->new(
            'sb-param-name', 'content' => $ptrHash->{'param_name'}
          ),
          XmlNode->new(
            'sb-param-value', 'content' => $ptrHash->{'param_value'}
          )
        ]
      )
    );
  }
  $serverNode->addChild($sbNode);
}

sub setGLServerSettings {
  my ( $self, $paramsPtr) = @_;

  my %params = %{$paramsPtr};

  my $serverNode = $self->{serverNode};

  my $glNode = XmlNode->new('grey-listing');

  $glNode->setAttribute('grey-interval', $params{'greyInterval'}) if defined $params{'greyInterval'};
  $glNode->setAttribute('expire-interval', $params{'expireInterval'}) if defined $params{'expireInterval'};
  $glNode->setAttribute('penalty-enabled', $params{'penaltyEnabled'}) if defined $params{'penaltyEnabled'};
  $glNode->setAttribute('penalty-interval', $params{'penaltyInterval'}) if defined $params{'penaltyInterval'};
  $glNode->setAttribute('enabled', $params{'enabled'}) if defined $params{'enabled'};
  $glNode->setAttribute('personal-conf', $params{'personal-conf'}) if defined $params{'personal-conf'};

  my $wlNode = XmlNode->new('white-list');

  foreach my $wdomain (@{$params{'white_domains'}}) {
        $wlNode->addChild(XmlNode->new('list-item', 'content' => $wdomain));
  }

  $glNode->addChild($wlNode);

  my $blNode = XmlNode->new('black-list');

  foreach my $bdomain (@{$params{'black_domains'}}) {
        $blNode->addChild(XmlNode->new('list-item', 'content' => $bdomain));
  }

  $glNode->addChild($blNode);

  $serverNode->addChild($glNode);

}

sub setControlsVisibility {
  my ( $self, $paramsPtr ) = @_;

  my %params = %{$paramsPtr};

  my $serverNode = $self->{serverNode};
  my $controlsVisibilityNode = XmlNode->new('controls-visibility');

  $controlsVisibilityNode->addChild( XmlNode->new( 'hide-domain-registration-buttons', 'content' => HelpFuncs::negation(HelpFuncs::checkValue($params{'domain_registration'},'true','false')) ) );
  $controlsVisibilityNode->addChild( XmlNode->new( 'hide-certificate-purchasing-buttons', 'content' => HelpFuncs::negation(HelpFuncs::checkValue($params{'cert_purchasing'},'true','false')) ) );
  $controlsVisibilityNode->addChild( XmlNode->new( 'hide-extra-services-buttons', 'content' => HelpFuncs::negation(HelpFuncs::checkValue($params{'extras'},'true','false')) ) );
  $controlsVisibilityNode->addChild( XmlNode->new( 'hide-mail-bouncing-controls', 'content' => HelpFuncs::negation(HelpFuncs::checkValue($params{'mail_bounce'},'true','false')) ) );

  $controlsVisibilityNode->addChild( XmlNode->new( 'domain-registration-url', 'content' => $params{'domain_registration_url'})) if defined $params{'domain_registration_url'};
  $controlsVisibilityNode->addChild( XmlNode->new( 'domain-management-url', 'content' => $params{'domain_management_url'})) if defined $params{'domain_management_url'};
  $controlsVisibilityNode->addChild( XmlNode->new( 'cert-purchasing-url', 'content' => $params{'cert_purchasing_url'})) if defined $params{'cert_purchasing_url'};
  $controlsVisibilityNode->addChild( XmlNode->new( 'mpc-portal-url', 'content' => $params{'mpc_portal_url'})) if defined $params{'mpc_portal_url'};

  $serverNode->getChild( 'interface-preferences', 1 )->addChild($controlsVisibilityNode);
}

sub setServerBackupSettings {
  my ( $self, $paramsPtr ) = @_;

  my %params = %{$paramsPtr};

  my $serverNode = $self->{serverNode};

  my ($lowPriority,$doNotCompress,$maxProcesses);

  $lowPriority = $params{'bu_nice'};
  $doNotCompress = $params{'bu_nozip'};
  $maxProcesses = $params{'max_bu_proc_number'};

  if ( defined $lowPriority or defined $doNotCompress or defined $maxProcesses ) {
  	my $serverBackupSettingsNode = XmlNode->new('backup-settings');

  	$serverBackupSettingsNode->setAttribute( 'low-priority', $lowPriority)
			if defined $lowPriority and $lowPriority eq 'true';

  	$serverBackupSettingsNode->setAttribute( 'do-not-compress', $doNotCompress)
			if defined $doNotCompress and $doNotCompress eq 'true';

		$serverBackupSettingsNode->setAttribute( 'max-processes', $maxProcesses)
  		if defined $maxProcesses;

  	$serverNode->addChild($serverBackupSettingsNode);
  }
}

sub addServerPreferences {
  my ( $self, $paramsPtr ) = @_;

  my %params = %{$paramsPtr};

  my $serverNode = $self->{serverNode};

  my $serverPrefsNode = XmlNode->new('server-preferences');

  if ( defined $params{'forbid_create_dns_subzone'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new(
        'forbid-create-dns-subzone',
        'content' => $params{'forbid_create_dns_subzone'}
      )
    );
  }
  if ( defined $params{'force_db_user_prefix'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new(
        'force-db-user-prefix',
        'content' => $params{'force_db_user_prefix'}
      )
    );
  }

  if ( defined $params{'db_user_length'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new(
        'db-user-length', 'content' => $params{'db_user_length'}
      )
    );
  }

  if ( defined $params{'hide_top_advertisement'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new(
        'hide-top-advertisement',
        'attributes' => { 'hide' => $params{'hide_top_advertisement'} }
      )
    );
  }
  if ( defined $params{'traffic_accounting'} ) {
    my $val = $params{'traffic_accounting'};
    if    ( $val == 1 ) { $val = 'in'; }
    elsif ( $val == 2 ) { $val = 'out'; }
    else                { $val = 'both'; }
    $serverPrefsNode->addChild(
      XmlNode->new(
        'traffic-direction', 'attributes' => { 'traffic' => $val }
      )
    );
  }
  if ( defined $params{'restart_apache_interval'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new(
        'restart-apache', 'content' => $params{'restart_apache_interval'}
      )
    );
  }
  if ( defined $params{'stat_ttl'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new( 'stat-keep', 'content' => $params{'stat_ttl'} ) );
  }
  if ( defined $params{'size_count_type'} ) {
    $serverPrefsNode->addChild(
      XmlNode->new(
        'disk-space-count-type',
        'attributes' => {
            'count-type' => $params{'size_count_type'} eq 'byte'
          ? 'byte'
          : 'block'
        }
      )
    );
  }

  my $duNode = XmlNode->new('disk-usage');
  $serverPrefsNode->addChild($duNode);
  $duNode->addChild( XmlNode->new('include-logs') )
    if defined $params{'include_logs'}
      and $params{'include_logs'} eq 'true';
  $duNode->addChild( XmlNode->new('include-databases') )
    if defined $params{'include_databases'}
      and $params{'include_databases'} eq 'true';
  $duNode->addChild( XmlNode->new('include-mailboxes') )
    if defined $params{'include_mailboxes'}
      and $params{'include_mailboxes'} eq 'true';
  $duNode->addChild( XmlNode->new('include-webapps') )
    if defined $params{'include_webapps'}
      and $params{'include_webapps'} eq 'true';
  $duNode->addChild( XmlNode->new('include-maillists') )
    if defined $params{'include_maillists'}
      and $params{'include_maillists'} eq 'true';
  $duNode->addChild( XmlNode->new('include-domaindumps') )
    if defined $params{'include_domaindumps'}
      and $params{'include_domaindumps'} eq 'true';
  $duNode->addChild( XmlNode->new('include-admindumps') )
    if defined $params{'include_admindumps'}
      and $params{'include_admindumps'} eq 'true';

  $serverPrefsNode->addChild( XmlNode->new( 'force-db-prefix', 'content' => $params{'force_db_prefix'} ) ) if defined $params{'force_db_prefix'};
  $serverPrefsNode->addChild( XmlNode->new( 'multiple-session', 'content' => $params{'multiply_login'} ) ) if defined $params{'multiply_login'};
  $serverPrefsNode->addChild( XmlNode->new( 'lock-screen', 'content' => ($params{'disable_lock_screen'} eq 'true' ? 'false' : 'true' ) ) ) if defined $params{'disable_lock_screen'};

  $serverPrefsNode->addChild( XmlNode->new( 'aps-catalog-url', 'content' => $params{'apscatalog_url'} ) ) if defined $params{'apscatalog_url'};

  $serverPrefsNode->addChild( XmlNode->new( 'mode', 'content' => (defined($params{'power_user_panel'}) and $params{'power_user_panel'} eq 'true') ? 'poweruser' : 'standard'));

  $serverNode->addChild($serverPrefsNode);
}

sub addServerMailmanConfiguration {
  my ($self) = @_;

  unless ( defined Mailman::version() ) {
    Logging::debug("Unable to found Mailman installation");
    return;
  }

  if (Mailman::isMailmanNotConfigured() eq '1') {
    Logging::debug("Mailman is not configured. Nothing to backup");
    return;
  }

  my $adminPassword =  Mailman::getListPassword('mailman');
  my @owners = Mailman::getListOwners('mailman');

  if (defined $adminPassword and defined $owners[0]) {
    my $root = $self->{serverNode};

    my $mailmanNode = XmlNode->new('mailman');

    $mailmanNode->setAttribute('owner', $owners[0]);
    $mailmanNode->setAttribute('password', $adminPassword);

    $root->addChild($mailmanNode);
  }
}

sub addDisableMailUiOption {
  my ($self, $state) = @_;
  my $serverNode = $self->{serverNode};
  my $serverPrefNode = $serverNode->getChild( 'server-preferences', 1 );
  my $disableUiNode = XmlNode->new('disable-mail-ui', 'content' => $state);
  $serverPrefNode->addChild($disableUiNode);
}

sub _makeExternalWebmails {
  my ($self, $webmailsPtr) = @_;
  my $serverNode = $self->{serverNode};
  my $mailPrefNode = $serverNode->getChild( 'mail-settings', 1 );
  my @webmails = @{$webmailsPtr};

  foreach my $webmail (@webmails) {
    my $extWebMailNode = XmlNode->new('external-webmail');
    $extWebMailNode->addChild( XmlNode->new('name', 'content' => $webmail->{'name'}) );
    $extWebMailNode->addChild( XmlNode->new('url', 'content' => $webmail->{'url'}) );
    $extWebMailNode->addChild( XmlNode->new('enabled') ) if $webmail->{'enabled'} == 1;
    $mailPrefNode->addChild($extWebMailNode);
  }
}

sub addRestrictionItem {
  my ( $self, $type, $ip, $mask ) = @_;

  $ip = '' unless defined $ip;
  $mask = '' unless defined $mask;

  my $serverNode = $self->{serverNode};

  my $serverPrefNode = $serverNode->getChild( 'server-preferences', 1 );

  my $adminAccessRestrictionsNode = $serverPrefNode->getChild( 'admin-access-restrictions', 1 );

  my $restrictionItemNode = XmlNode->new( 'restriction-item' );
  $restrictionItemNode->setAttribute( 'type', $type eq 'allow'? 'allow' : 'deny' );
  $restrictionItemNode->setAttribute( 'ip-address', $ip );
  $restrictionItemNode->setAttribute( 'ip-subnet-mask', $mask );

  $adminAccessRestrictionsNode->addChild( $restrictionItemNode );
}

sub addRootAdmin {
  my ( $self, $id, $guid ) = @_;

  return if defined $self->{admin};

  my $rootNode = $self->{root};
  if ( !defined($rootNode) ) {
    Logging::warning('Unable to dump Panel admin user, because of dump XML structure is not full', 'PackerStructure');
    return;
  }
  my $root = XmlNode->new( 'admin' );
  $self->{admin} = $root;
  $root->setAttribute( 'id', $id ) if $id;
  $root->setAttribute( 'guid', $guid ) if $guid;

  $rootNode->addChild($root);
}

### Client functions ##

sub addRootClient {
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status ) = @_;

  my $rootNode = $self->{root};
  if ( !defined($rootNode) ) {
    Logging::warning('Unable to dump root client user, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $root = $self->makeClientNode($clientId, $clientPtr, $passwdPtr, $status, 1);

  $rootNode->addChild($root);
  push @{$self->{roots}}, $root;
}

sub addRootReseller{
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status ) = @_;

  my $rootNode = $self->{root};
  if ( !defined($rootNode) ) {
    Logging::warning('Unable to dump reseller, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $root = $self->makeResellerNode($clientId, $clientPtr, $passwdPtr, $status, 1);

  $rootNode->addChild($root);
  push @{$self->{roots}}, $root;
}

sub addResellerClient {
  my ( $self, $resellerId, $clientId, $clientPtr, $passwdPtr, $status ) = @_;

  my %client = %{$clientPtr};
  my %passwd = %{$passwdPtr};

  my $resellerNode = $self->{resellersNodes}->{$resellerId};
  if (!defined($resellerNode))
  {
    Logging::warning('Unable to dump reseller\'s client, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $root = $self->makeClientNode($clientId, \%client, \%passwd, $status, 0);

  $resellerNode->getChild( 'clients', 1 )->addChild($root);
  $self->regClientObjectBackupPath( $self->getResellersBackupPath( $resellerId ), $clientId, $clientPtr->{'login'} );

  if (exists $self->{resellersShortNodes}->{$resellerId}) {
    $self->{resellersShortNodes}->{$resellerId}->addChild($self->{clientsShortNodes}->{$clientId});
  }
}

sub addAdminClient {
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status ) = @_;

  my %client = %{$clientPtr};
  my %passwd = %{$passwdPtr};

  my $admin = $self->{admin};
  if (!defined($admin))
  {
    Logging::warning('Unable to dump admin\'s client, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $root = $self->makeClientNode($clientId, \%client, \%passwd, $status, 0);

  $admin->getChild( 'clients', 1 )->addChild($root);
  $self->regClientObjectBackupPath( $self->getAdminBackupPath(), $clientId, $clientPtr->{'login'} );
}

sub addAdminReseller {
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status ) = @_;

  my $admin = $self->{admin};
  if (!defined($admin))
  {
    Logging::warning('Unable to dump admin\'s reseller, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $root = $self->makeResellerNode($clientId, $clientPtr, $passwdPtr, $status, 0);

  $admin->getChild( 'resellers', 1 )->addChild($root);
  $self->regResellersObjectBackupPath( $self->getAdminBackupPath(), $clientId, $clientPtr->{'login'} );
}

sub makeResellerNode{
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status, $needFullInfo ) = @_;

  my $root = $self->processClientNode( $clientId, $clientPtr, $passwdPtr, $status, 1, $needFullInfo );

  return $root;

}

sub makeClientNode {
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status, $needFullInfo ) = @_;

  my $root = $self->processClientNode( $clientId, $clientPtr, $passwdPtr, $status, 0, $needFullInfo );

  return $root;
}

sub processClientNode {
  my ( $self, $clientId, $clientPtr, $passwdPtr, $status, $is_reseller, $needFullInfo ) = @_;

  my $root = XmlNode->new( $is_reseller ? 'reseller' : 'client');

  my %client = %{$clientPtr};
  my %passwd = %{$passwdPtr};

  unless ($self->{capabilityOnlyMode}) {

  $root->getChild( 'preferences', 1 );
  $root->getChild( 'properties', 1 );
    $root->getChild( 'preferences', 1 );
    $root->getChild( 'properties', 1 );

    $root->setAttribute( 'id', $clientId );

    if ( defined $client{'uid'} ) {
      $root->setAttribute( 'uid', $client{'uid'} );
      $root->setAttribute( 'ownership', $client{'ownership'} eq 'true' ? 'true' : 'false' );
    }

    $root->setAttribute( 'guid', $client{'guid'} ) if ( defined $client{'guid'} );
    $root->setAttribute( 'external-id', $client{'external_id'} ) if ( defined $client{'external_id'} and $client{'external_id'} ne '');
    $root->setAttribute( 'vendor-guid', $client{'vendor-guid'} ) if ( defined $client{'vendor-guid'} );
    $root->setAttribute( 'vendor-login', $client{'vendor-login'} ) if ( defined $client{'vendor-login'} );

    $root->setAttribute( 'owner-guid', $client{'owner-guid'} ) if ( defined $client{'owner-guid'} and not $is_reseller );

    if ( exists $client{'login'} ) {
      $root->setAttribute( 'name', ( defined $client{'login'} )? $client{'login'}:'' );
    }
    if ( exists $client{'pname'} ) {
      $root->setAttribute( 'contact', ( defined $client{'pname'} )? $client{'pname'}:'' );
    }
    if ( exists $client{'cr_date'} ) {
      $root->setAttribute( 'cr-date', ( defined $client{'cr_date'} )? $client{'cr_date'}:'' );
    }
  if ( exists $client{'owner-name'} and not $is_reseller ) {
    $root->setAttribute( 'owner-name', $client{'owner-name'} );
  }

    my $item;
    my $props = $root->getChild( 'properties', 1 );
    $item = CommonPacker::makePasswordNode( $passwd{'password'}, CommonPacker::normalizePasswordType( $passwd{'type'} ) );
    $props->addChild($item);

    $props->addChild( Status::make($status) );

  } else {

    if ( exists $client{'login'} ) {
      $root->addChild(XmlNode->new('name', 'content' => (defined $client{'login'}) ? $client{'login'} : ''));
    }
    if ( exists $client{'pname'} ) {
      $root->addChild(XmlNode->new('contact', 'content' => (defined $client{'pname'}) ? $client{'pname'} : ''));
    }

  }

  $self->{clientNodes}->{$clientId} = $root;
  $self->{resellersNodes}->{$clientId} = $root if $is_reseller;

  if ($needFullInfo or $self->{listingOnlyMode}) {
    return $root;
  } else {
    my $shortInfoNodeName = ($is_reseller ? 'reseller' : 'client') . '-info';
    my $shortInfoNode = XmlNode->new($shortInfoNodeName);
    $shortInfoNode->setAttribute('name', ( defined $client{'login'} )? $client{'login'}:'');
    $shortInfoNode->setAttribute( 'guid', $client{'guid'} ) if defined $client{'guid'};
    if ($is_reseller) {
      $self->{resellersShortNodes}->{$clientId} = $shortInfoNode;
    } else {
      $self->{clientsShortNodes}->{$clientId} = $shortInfoNode;
    }
    return $shortInfoNode;
  }

}

sub finishClient {
  my ( $self, $clientId ) = @_;

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to finish client node, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  #TODO: uncomment
  #$root->ReleaseCode();
}

sub setClientPinfo {
  my ( $self, $clientId, $clientPtr ) = @_;

  my %client = %{$clientPtr};

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump client personal information, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my ( $field, $name );

  my %clientsInfo = (
    'company' => 'cname',
    'phone'   => 'phone',
    'fax'     => 'fax',
    'address' => 'address',
    'city'    => 'city',
    'state'   => 'state',
    'zip'     => 'pcode',
    'country' => 'country',
    'email'   => 'email',
  );
  my $prefs = $root->getChild( 'preferences', 1 );
  while ( ( $name, $field ) = each(%clientsInfo) ) {
    if ( exists( $client{$field} ) && $client{$field} ) {
      $prefs->addChild(
        XmlNode->new(
          'pinfo',
          'content'    => $client{$field},
          'attributes' => { 'name' => $name }
        )
      );
    }
  }
}

sub setClientLocale {
  my ( $self, $clientId, $locale ) = @_;

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump client locale, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $root->getChild( 'preferences', 1 )->addChild(
    XmlNode->new(
      'pinfo',
      'content'    => $locale,
      'attributes' => { 'name' => 'locale' }
    )
  );
}

sub addClientLimit {
  my ( $self, $clientId, $name, $value ) = @_;

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump client limits, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $self->insertLimitNode( $root, $name, $value );
}

sub addClientPermission {
  my ( $self, $clientId, $name, $value ) = @_;

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump client permissions, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $self->makePermissionNode( $root->getChild( 'limits-and-permissions', 1 ), $name, $value );
}

sub addClientDomainSkeleton {
  my ( $self, $clientId, $path, $arc_path ) = @_;

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump client\'s domain skeleton, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $dumpFile = $self->{content_transport}->addClientContent( 'skeleton', $clientId, $arc_path, 'directory' => $path );
  $root->getChild( 'content', 1, 1 )->addChild( $dumpFile) if $dumpFile;
}

sub addServerSkeleton {
  my ( $self, $path, $arc_path) = @_;

  my $root = $self->{serverNode};

  my $dumpFile = $self->{content_transport}->addAdminContent('skeleton', undef, $arc_path, 'directory' => $path);

  $root->getChild('content', 1, 1)->addChild($dumpFile) if $dumpFile;
}

sub addSmbDbDump {
  my ($self, $options) = @_;
  my $root = $self->{serverNode};
  $options->{utf8names} = 1;
  my $cid = $self->{content_transport}->addDbContent('smb-sqldump', $self->getAdminBackupPath('smbdb'), %{$options});
  $root->getChild('content', 1, 1)->addChild($cid) if $cid;
}

sub addApsCache {
  my ($self, $path) = @_;
  my $root = $self->{serverNode};
  my $dumpFile = $self->{content_transport}->addAdminContent('aps-cache', undef, 'aps_cache', 'directory' => $path, 'checkEmptyDir' => 1 );
  $root->getChild('content', 1, 1)->addChild($dumpFile) if $dumpFile;
}

sub addResellerDomainsClientsNodes {
  my ( $self, $clientId ) = @_;

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump reseller\'s clients and domains, because of dump XML structure is not full', 'PackerStructure');
    return;
  }
  $root->addChild( XmlNode->new('domains') );
  $root->addChild( XmlNode->new('clients') );
}

sub removeSiteNode {
  my ( $self, $domainId, $siteName ) = @_;

  my $domainNode = $self->{domainNodes}->{$domainId};
  my $sitesNode = $domainNode->getChild( 'phosting' )->getChild( 'sites' );

  if ( defined $sitesNode ) {
    $sitesNode->removeChildByAttribute( 'site', 'name', $siteName );
  }
}

sub removeSubdomainNode {
  my ( $self, $domainId, $subdomainName ) = @_;

  my $domainNode = $self->{domainNodes}->{$domainId};
  my $sdNode = $domainNode->getChild( 'phosting' )->getChild( 'subdomains' );
  if ( defined $sdNode ) {
    $sdNode->removeChildByAttribute( 'subdomain', 'name', $subdomainName );
  }
}

sub removeDatabaseNode {
  my ( $self, $domainId, $db ) = @_;
  my $domainNode = $self->{domainNodes}->{$domainId};
  my $dbNode = $domainNode->getChild( 'databases' );
  if ( defined $dbNode ) {
    $dbNode->removeChildByAttribute( 'database', 'id', $db->{'id'} );
  }
}

sub removeServerNode {
  my ( $self ) = @_;
  $self->{serverNode} = undef;
}

sub removeMailuserNode {
  my ( $self, $domainId, $mail ) = @_;
  my $domainNode = $self->{domainNodes}->{$domainId};
  my $mailSystemNode = $domainNode->getChild( 'mailsystem' );
  my $mailUsersNode = $mailSystemNode->getChild( 'mailusers' );

  if ( defined $mailUsersNode ) {
    $mailUsersNode->removeChildByAttribute( 'mailuser', 'id', $mail->{'id'} );
  }
}

sub removeMailSystemNode {
  my ( $self, $domainId ) = @_;
  my $domainNode = $self->{domainNodes}->{$domainId};
  $domainNode->removeChildren( 'mailsystem' );
}

sub removeHostingNode {
  my ( $self, $domainId, $hostingType ) = @_;
  my $domainNode = $self->{domainNodes}->{$domainId};
  my $hostingNodeName;
  if ( $hostingType eq 'vrt_hst' )  {
    $hostingNodeName = 'phosting';
  } elsif ( $hostingType eq 'std_fwd' ) {
    $hostingNodeName = 'shosting';
  } elsif ( $hostingType eq 'frm_fwd' ) {
    $hostingNodeName = 'fhosting';
  }
  $domainNode->removeChildren( $hostingNodeName );
}

sub addClientIps {
  my ( $self, $clientId, $ipsPtr ) = @_;

  my %ips = %{$ipsPtr};

  my $root = $self->{clientNodes}->{$clientId};
  if ( !defined($root) ) {
    Logging::warning('Unable to dump client\'s IP settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $ipNodeName = $self->{capabilityOnlyMode} ? 'ip' : 'ip_pool';
  my $ipNode = XmlNode->new($ipNodeName);
  $root->addChild($ipNode);
  while( my($ip, $iptype) = each(%ips) ){
    $ipNode->addChild( $self->makeIpNode($ip, $iptype) );
  }

}

sub setClientTraffic{
  my ( $self, $clientId, $trafficValue ) = @_;

  my $parent = $self->{clientNodes}->{$clientId};
  if ( !defined($parent) ) {
    Logging::warning('Unable to dump client traffic, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $parent->addChild( XmlNode->new( 'traffic', 'content' => $trafficValue ) );
}

sub addResellerDomainTemplate {
  my $self = shift;
  $self->addClientDomainTemplate( @_ );
}

sub addClientDomainTemplate {
  my ( $self, $clientId, $templateName, $dataPtr ) = @_;

  my $root = $self->{clientNodes}->{$clientId};

  my $node = $self->makeTemplateNode('domain-template', $templateName, $dataPtr);

  $root->getChild( 'preferences', 1 )->addChild($node);
}

sub addClientCustomButton {
    my ( $self, $clientId, $id, $optionsPtr, $customButtonsDir, $icon ) = @_;

    my $parent = $self->{clientNodes}->{$clientId};
    if ( !defined($parent) ) {
      Logging::warning('Unable to dump client\'s custom buttons settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $node = $self->makeCustomButtonNode( 'clients', $clientId, $id, $optionsPtr, $customButtonsDir,
      $icon );

    $parent->getChild( 'preferences', 1 )->addChild($node);
}

sub setPpbConnection {
    my ($self, $clientParams) = @_;

    my $ppbUrl = undef;
    my $apiVersion = undef;

    my $parent;

    $parent = $self->{serverNode};

    $ppbUrl = $clientParams->{'ppb-url'} if exists $clientParams->{'ppb-url'};

    if (defined $ppbUrl && defined $apiVersion) {
        my $ppbConnectionNode = XmlNode->new('ppb-connection');
        $ppbConnectionNode->addChild(XmlNode->new('ppb-url', 'content' => $ppbUrl));
        $parent->getChild( 'properties', 1)->addChild($ppbConnectionNode);
    }
}

### Domain functions ##
my @_rootDomains;

sub addRootDomain {
  my ( $self, $domainId, $domainName, $domainAsciiName, $domainPtr, $ownerGuid ) = @_;
  $domainAsciiName = $domainName if not $domainAsciiName;

  my $needFullInfo = !$self->{migrationMode} || $self->{listingOnlyMode};
  my $root = $self->makeDomainNode($domainId, $domainName, $domainPtr, 0, $needFullInfo);
  push @_rootDomains, $root;
}

sub addRootDomains {
  my $self = shift;

  my $rootNode = $self->{root};
  if ( !defined($rootNode) ) {
    Logging::warning('Unable to dump root domains, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  for my $domainNode ( @_rootDomains ) {
    $rootNode->addChild($domainNode);
    push @{$self->{roots}}, $domainNode;
  }
}

sub addAdminDomain {
    my ( $self, $domainId, $domainName, $domainAsciiName, $domainPtr ) = @_;
    $domainAsciiName = $domainName if not $domainAsciiName;

    my $admin = $self->{admin};
    if ( !defined($admin) ) {
      Logging::warning('Unable to dump admin\'s domain, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $root = $self->makeDomainNode($domainId, $domainName, $domainPtr, 0, 0);

    $admin->getChild( 'domains', 1 )->addChild($root);
    $self->regDomainObjectBackupPath( $self->getAdminBackupPath(), $domainId, $domainAsciiName );

}

sub addResellerDomain {
    my ( $self, $resellerId, $domainId, $domainName, $domainAsciiName, $domainPtr ) = @_;
    $domainAsciiName = $domainName if not $domainAsciiName;

    my $resellerNode = $self->getCashedClientNode($resellerId);
    return unless defined $resellerNode;

    my $root = $self->makeDomainNode($domainId, $domainName, $domainPtr, 0, 0);

    $resellerNode->getChild( 'domains', 1 )->addChild($root);
    $self->regDomainObjectBackupPath( $self->getResellersBackupPath( $resellerId ), $domainId, $domainAsciiName );

    if (exists $self->{resellersShortNodes}->{$resellerId}) {
      $self->{resellersShortNodes}->{$resellerId}->addChild($self->{domainShortNodes}->{$domainId});
    }
}

sub addClientDomain {
    my ( $self, $clientId, $domainId, $domainName, $domainAsciiName, $domainPtr ) = @_;
    $domainAsciiName = $domainName if not $domainAsciiName;

    my $clientNode = $self->getCashedClientNode($clientId);
    return unless defined $clientNode;

    my $root = $self->makeDomainNode($domainId, $domainName, $domainPtr, 0, 0);

    $clientNode->getChild( 'domains', 1 )->addChild($root);
    $self->regDomainObjectBackupPath( $self->getClientsBackupPath( $clientId ), $domainId, $domainAsciiName );

    if (exists $self->{clientsShortNodes}->{$clientId}) {
      $self->{clientsShortNodes}->{$clientId}->addChild($self->{domainShortNodes}->{$domainId});
    }
}

my %_rootUsers;
my %_rootRoles;

sub addDomainSite {
    my ( $self, $domainId, $siteId, $siteAsciiName, $siteName, $sitePtr ) = @_;

    my $domainNode = $self->{domainNodes}->{$domainId};
    if ( !defined($domainNode) ) {
      Logging::warning('Unable to dump domain site, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $siteNode = $self->makeDomainNode($siteId, $siteName, $sitePtr, 1, 1);
    my $phostingNode = $domainNode->getChild( 'phosting', 1 );
    $phostingNode->getChild( 'sites', 1 )->addChild($siteNode);
    $self->regSiteObjectBackupPath( $self->getDomainsBackupPath( $domainId ), $siteId, $siteAsciiName );
}

sub addAdminUser {
    my ( $self, $userName, $userPtr ) = @_;
  my ( $self, $userName, $userPtr ) = @_;

  my $adminNode = $self->{admin};
  if ( !defined($adminNode) ) {
    Logging::warning('Unable to dump admin user, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $userNode = $self->makeUserNode($userName, $userPtr);
  $adminNode->getChild( 'users', 1 )->addChild($userNode) if defined $userNode;
}

sub addAdminPermission {
  my ( $self, $name, $value ) = @_;

  my $adminNode = $self->{admin};
  if ( !defined($adminNode) ) {
    Logging::warning('Unable to dump admin permissions, because of dump XML structure is not full', 'PackerStructure');
    return;
  }
  $self->makePermissionNode( $adminNode->getChild( 'limits-and-permissions', 1 ), $name, $value );
}

sub addClientUser {
    my ( $self, $ownerId, $userName, $userPtr ) = @_;

    my $clientNode = $self->getCashedClientNode($ownerId);
    return unless defined $clientNode;

    my $userNode = $self->makeUserNode($userName, $userPtr);
    $clientNode->getChild( 'users', 1 )->addChild($userNode) if defined $userNode;
}

sub removeClientUser {
  my ( $self, $ownerId, $userName ) = @_;

  my $clientNode = $self->getCashedClientNode($ownerId);
  my $usersNode = $clientNode->getChild( 'users' );

  if ( defined $usersNode ) {
    $usersNode->removeChildByAttribute( 'user', 'name', $userName );
  }
}

sub addRootUser {
  my ( $self, $userName, $userPtr ) = @_;

  if (!exists $_rootUsers{$userName}) {
    my $userNode = $self->makeUserNode($userName, $userPtr);
    $_rootUsers{$userName} = $userNode if defined $userNode;
  }
}

sub removeRootUser {
  my ( $self, $userName ) = @_;
  if ( exists $_rootUsers{$userName} ) {
    delete $_rootUsers{$userName};
  }
}

sub addRootUsers {
    my ( $self ) = @_;

    my $rootNode = $self->{root};
    if ( !defined($rootNode) ) {
      Logging::warning('Unable to dump root users, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    if (%_rootUsers) {
      foreach my $userName (keys %_rootUsers) {
        $rootNode->addChild($_rootUsers{$userName});
      }
    }
}

sub addRoleToRoles {
    my ( $self, $roleNode, $rolesNode ) = @_;

    my @roles  = $rolesNode->getChildren( 'role' );
    foreach my $role ( @roles ) {
      return if $role->getAttribute('name') eq $roleNode->getAttribute('name');
    }
    $rolesNode->addChild( $roleNode );
}

sub addAdminRole {
    my ( $self, $roleName, $isBuiltIn, $permsPtr, $servicePermissions ) = @_;

    my $adminNode = $self->{admin};
    if ( !defined($adminNode) ) {
      Logging::warning('Unable to dump admin role, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $roleNode = $self->makeRoleNode($roleName, $isBuiltIn, $permsPtr, $servicePermissions);
    $self->addRoleToRoles( $roleNode, $adminNode->getChild( 'roles', 1 ) );
}

sub removeAdminRole {
  my ( $self, $roleName ) = @_;

  my $adminNode = $self->{admin};
  my $rolesNode = $adminNode->getChild( 'roles' );

  if ( defined $rolesNode ) {
    $rolesNode->removeChildByAttribute( 'role', 'name', $roleName );
  }
}

sub addClientRole {
  my ( $self, $ownerId, $roleName, $isBuiltIn, $permsPtr, $servicePermissions ) = @_;

  my $clientNode = $self->getCashedClientNode($ownerId);
  return unless defined $clientNode;

  my $roleNode = $self->makeRoleNode($roleName, $isBuiltIn, $permsPtr, $servicePermissions);
  $self->addRoleToRoles( $roleNode, $clientNode->getChild( 'roles', 1 ) );
}

sub removeClientRole {
  my ( $self, $ownerId, $roleName ) = @_;

  my $clientNode = $self->getCashedClientNode($ownerId);
  my $rolesNode = $clientNode->getChild( 'roles' );

  if ( defined $rolesNode ) {
    $rolesNode->removeChildByAttribute( 'role', 'name', $roleName );
  }
}

sub addRootRole {
  my ( $self, $roleName, $isBuiltIn, $permsPtr, $servicePermissions ) = @_;

  if (!exists $_rootRoles{$roleName}) {
      my $roleNode = $self->makeRoleNode($roleName, $isBuiltIn, $permsPtr, $servicePermissions);
      $_rootRoles{$roleName} = $roleNode if defined $roleNode;
    }
}

sub removeRootRole {
  my ( $self, $roleName ) = @_;
  if ( exists $_rootRoles{$roleName} ) {
    delete $_rootRoles{$roleName};
  }
}

sub addRootRoles {
  my ( $self ) = @_;

  my $rootNode = $self->{root};
  if ( !defined($rootNode) ) {
    Logging::warning('Unable to dump root roles, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  if (%_rootRoles) {
    foreach my $roleName (keys %_rootRoles) {
      $self->addRoleToRoles( $_rootRoles{$roleName}, $rootNode );
    }
  }
}

sub getCashedClientNode {
  my ($self, $clientId) = @_;
  my $root = $self->{resellersNodes}->{$clientId};
  unless ( defined($root) ) {
    $root = $self->{clientNodes}->{$clientId};
  }
  return $root if defined($root);

  my $caller = (caller(1))[3];
  if (defined $caller) {
    Logging::warning("Error in $caller : empty client parent node for id ($clientId)", 'PackerStructure');
  }
  else {
    Logging::warning('Unable to dump client settings, because of dump XML structure is not full', 'PackerStructure');
  }
  return;
}

my %_requiredLimits = (
  domain => ['max_subdom', 'max_dom_aliases', 'disk_space', 'max_traffic', 'max_wu', 'max_db',
             'max_box', 'mbox_quota', 'max_maillists', 'max_webapps', 'expiration', 'max_site'],
  client => ['max_dom', 'max_subdom', 'max_dom_aliases', 'disk_space', 'max_traffic', 'max_wu',
             'max_db', 'max_box', 'mbox_quota', 'max_maillists', 'max_webapps', 'expiration'],
  reseller => ['max_cl', 'max_dom', 'max_subdom', 'max_dom_aliases', 'disk_space', 'max_traffic', 'max_wu',
               'max_db', 'max_box', 'mbox_quota', 'max_maillists', 'max_webapps', 'expiration']
);

sub fixDefaultLimits {
  my ($self, $type, $id) = @_;
  my $node;
  my $requiredLimits;

  if ( ($type eq 'client') || ($type eq 'reseller') ) {
    $node = $self->getCashedClientNode($id);
  }
  elsif ($type eq 'domain') {
    $node = $self->getCashedDomainNode($id);
  }
  return unless defined $node;

  $requiredLimits = $_requiredLimits{$type};

  my %usedLimits;
  XPath::Select $node, 'limits-and-permissions/limit', sub {
    $usedLimits{ shift->getAttribute( 'name' ) } = 1;
  };

  for my $name ( @{$requiredLimits} ) {
    if ( !exists( $usedLimits{$name} ) ) {
      my $value = ( $name eq 'max_site' ) ? '1' : '-1';
      $self->insertLimitNode( $node, $name, $value );
    }
  }
}

my %smbUserPinfo = (
  'phone'         => 'phone',
  'phoneType'     => 'phone-type',
  'phone2'        => 'phone2',
  'phone2Type'    => 'phone2-type',
  'phone3'        => 'phone3',
  'phone3Type'    => 'phone3-type',
  'imNumber'      => 'im',
  'imType'        => 'im-type',
  'additionalInfo' =>  'comment',
  'email'         =>  'email',
  'companyName'   =>  'company',
  'fax'           =>  'fax',
  'address'       =>  'address',
  'city'          =>  'city',
  'state'         =>  'state',
  'zip'           =>  'zip',
  'country'       =>  'country'
);

sub makeUserNode {
  my ( $self, $userName, $userPtr ) = @_;

  my %user = %{$userPtr};

  my $userNode = XmlNode->new( 'user' );

  $userNode->setAttribute( 'name', $userName );

  if (defined $user{'contactName'}) {
    $userNode->setAttribute( 'contact', $user{'contactName'} );
  } else {
    $userNode->setAttribute( 'contact', $userName );
  }
  $userNode->setAttribute( 'guid',        $user{'uuid'}) if defined $user{'uuid'};
  $userNode->setAttribute( 'email',       $user{'email'} ) if defined $user{'email'};
  $userNode->setAttribute( 'is-built-in', $user{'isBuiltIn'} ? 'true' : 'false' ) if exists $user{'isBuiltIn'};
  $userNode->setAttribute( 'cr-date',     $user{'creationDate'} ) if exists $user{'creationDate'};
  $userNode->setAttribute( 'external-id', $user{'externalId'} ) if defined $user{'externalId'};

  if (defined $user{'subscriptionDomainId'} and $user{'subscriptionDomainId'} != 0) {
    my $subscriptionName = PleskStructure::getDomainNameFromId($user{'subscriptionDomainId'});
    if ($subscriptionName) {
      $userNode->setAttribute( 'subscription-name', $subscriptionName );
    }else{
      $userNode->setAttribute( 'subscription-name', 'removed');
    }
  }

  $userNode->setAttribute( 'is-domain-admin', (defined $user{'isDomainAdmin'} and ( $user{'isDomainAdmin'} eq '1' ) )? 'true' : 'false' );
  $userNode->setAttribute( 'is-legacy-user', (defined $user{'isLegacyUser'} and ( $user{'isLegacyUser'} eq '1' ) ) ? 'true' : 'false' );
  $userNode->setAttribute( 'file-sharing-id', $user{'fileSharingId'} ) if defined $user{'fileSharingId'};
  $userNode->setAttribute( 'external-email', 'true' ) if $user{'isExternal'} eq '1';

  my $propsNode = XmlNode->new('properties');
  my $passwordNode = CommonPacker::makePasswordNode($userPtr->{'password'}, (defined $userPtr->{'passwordType'})? $userPtr->{'passwordType'} : 'plain', (defined $userPtr->{'isBase64'} and $userPtr->{'isBase64'} eq '0') ? undef : 'base64');
  $propsNode->addChild($passwordNode);

  my $status = $userPtr->{'isLocked'} ? $Status::ADMIN : $Status::ENABLED;
  $propsNode->addChild(Status::make($status));
  $userNode->addChild($propsNode);

  my $lpNode = $userNode->getChild('limits-and-permissions', 1);
  $lpNode->addChild(XmlNode->new('role-ref', 'content' => $userPtr->{'SmbRoleName'}));

  foreach my $app (@{$user{'assignedApplications'}}) {
    $lpNode->addChild(XmlNode->new('assigned-application', 'content' => $app));
  }

  my $prefsNode = XmlNode->new('preferences');

  my @usersPhones;
  foreach my $pinfo (keys %user) {
    next unless defined $smbUserPinfo{$pinfo};
    if ( defined $user{$pinfo} and $user{$pinfo} ne '' ) {
      next if ( $smbUserPinfo{$pinfo} =~ /phone(\d?)-type/);
      if ($smbUserPinfo{$pinfo} =~ /^phone(\d?)$/) {
        push @usersPhones,  $user{$pinfo};
        next;
      }
      $prefsNode->addChild(XmlNode->new('pinfo',
        'attributes' => {'name' => $smbUserPinfo{$pinfo} },
        'content' => $user{$pinfo}
      ));
    }
  }

  if (@usersPhones) {
    $prefsNode->addChild(
      XmlNode->new(
        'pinfo',
        'attributes' => { 'name' => 'phone' },
        'content' => join(',', @usersPhones)
      )
    );
  }

  $userNode->addChild($prefsNode);

  return $userNode;
}

sub makeRoleNode {
  my ( $self, $roleName, $isBuiltIn, $permsPtr, $servicePermissions ) = @_;

  my $roleNode = XmlNode->new( 'role' );
  $roleNode->setAttribute( 'name', $roleName );
  $roleNode->setAttribute( 'is-built-in', $isBuiltIn? 'true' : 'false' );

  my $limitsAndPermsNode = XmlNode->new( 'limits-and-permissions' );
  $roleNode->addChild($limitsAndPermsNode);

  while ( my ( $name, $value ) = each( %{$permsPtr} ) ) {
    my $permNode = XmlNode->new( 'permission', 'attributes' => {
                                                'name'  => $name,
                                                'value' => $value? 'true' : 'false' }
                               );
    $limitsAndPermsNode->addChild( $permNode );
  }

  for my $perm (@$servicePermissions) {
    if ($perm->{classname} == "Smb_Service_Provider_Aps") {
      my $permNode = XmlNode->new('service-permission', 'attributes' => {
        'classname' => $perm->{'classname'},
        'description' => $perm->{'description'},
        'externalId' => $perm->{'externalId'},
        'permissionCode' => $perm->{'permissionCode'},
        'class' => $perm->{'class'},
        'value' => 'true',
      });
      $limitsAndPermsNode->addChild( $permNode );
    }
  }

  return $roleNode;
}

sub makeDomainNode {
    my ( $self, $domainId, $domainName, $domainPtr, $issite, $needFullInfo ) = @_;

    unless ( ref($domainPtr) =~ /HASH/ ) {
      Logging::warning("Unable to backup domain $domainName");
      return;
    }

    my %domain = %{$domainPtr};

    my $nodeName = ($issite? 'site' : 'domain');
    my $root = XmlNode->new($nodeName);

    unless ($self->{capabilityOnlyMode}) {
      $root->setAttribute( 'id', $domainId );
      $root->setAttribute( 'name', $domainName );
      $root->setAttribute( 'cr-date', $domain{'cr_date'} ) if exists $domain{'cr_date'};
      $root->setAttribute( 'guid', $domain{'guid'} ) if defined $domain{'guid'};
      $root->setAttribute( 'external-id', $domain{'external_id'} ) if ( not $issite and defined $domain{'external_id'} and $domain{'external_id'} ne '');
      $root->setAttribute( 'external-id', $domain{'external_id'} ) if ( defined $domain{'external_id'} and $domain{'external_id'} ne '');

      $root->setAttribute( 'vendor-guid', $domain{'vendor-guid'} ) if ( not $issite and defined $domain{'vendor-guid'} );
      $root->setAttribute( 'owner-guid', $domain{'owner-guid'} ) if ( not $issite and defined $domain{'owner-guid'} );
      $root->setAttribute( 'owner-name', $domain{'owner-name'} ) if ( not $issite and defined $domain{'owner-name'} );
      $root->setAttribute( 'parent-domain-name', $domain{'parentDomainName'} ) if ( $issite and defined $domain{'parentDomainName'} );
      $root->setAttribute( 'parent-domain-name', $domain{'parentDomainName'} ) if ( $issite and defined $domain{'parentDomainName'} );

      $root->setAttribute( 'vendor-login', $domain{'vendor-login'} ) if ( not $issite and defined $domain{'vendor-login'} );
      $root->getChild( 'preferences', 1 );
      $root->getChild( 'properties', 1 );
      $root->getChild( 'limits-and-permissions', 1 ) unless $issite;
    } else {
      $root->addChild(XmlNode->new('name', 'content' => $domainName))
    }

    if($issite) {
      $self->{siteNodes}->{$domainId} = $root;
    }
    else {
      $self->{domainNodes}->{$domainId} = $root;
    }

    if ($needFullInfo or $self->{listingOnlyMode}) {
      return $root;
    } else {
      my $shortInfoNode = XmlNode->new('domain-info');
      $shortInfoNode->setAttribute('name', $domainName);
      $shortInfoNode->setAttribute('guid', $domain{'guid'}) if defined $domain{'guid'};
      unless($issite) {
        $self->{domainShortNodes}->{$domainId} = $shortInfoNode;
      }
      return $shortInfoNode;
    }
}

sub getCashedDomainNode {
  my ($self, $domainId) = @_;
  my $root = $self->{domainNodes}->{$domainId};
  unless ( defined($root) ) {
    $root = $self->{siteNodes}->{$domainId};
  }
  return $root if defined($root);

  my $caller = (caller(1))[3];
  if (defined $caller) {
    Logging::warning("Error in $caller : empty domain parent node for id ($domainId)", 'PackerStructure');
  }
  else {
    Logging::warning('Error: empty domain parent node', 'PackerStructure');
  }
  return;
}

sub finishDomain {
  my ( $self, $domainId) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  #TODO: uncomment
  #$root->ReleaseCode();
}

sub setDomainWwwStatus {
  my ( $self, $domainId, $status ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  $root->setAttribute( 'www', $status );
}

sub setDomainIP {
  my ( $self, $domainId, $ip, $iptype ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $ipsNodeName = $self->{capabilityOnlyMode} ? 'ip' : 'properties';
  $root->getChild( $ipsNodeName, 1 )->addChild( $self->makeIpNode( $ip, $iptype ), 1 );
}

sub setDomainStatus {
  my ( $self, $domainId, $status ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  $root->getChild( 'properties', 1 )->addChild( Status::make($status) );
}

sub setWebspaceStatus {
  my ( $self, $domainId, $status, $siteStatus ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  if (not defined $siteStatus) {
    $root->getChild( 'properties', 1 )->addChild( Status::make($status, 'webspace-status') );
    $root->getChild( 'properties', 1 )->addChild( Status::make($status) );
  } else {
    $root->getChild( 'properties', 1 )->addChild( Status::make($status, 'webspace-status') );
    $root->getChild( 'properties', 1 )->addChild( Status::make($siteStatus) );
  }
}

sub getDomainARecordIp {
  my ( $self, $domainId) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $name = PleskStructure::getDomainNameFromId($domainId);
  my $aRecordIp;
  XPath::Select $root, 'properties/dns-zone/dnsrec', sub {
    my $rec = shift;
    if ((($rec->getAttribute('type') eq 'A') or ($rec->getAttribute('type') eq 'AAAA')) and ($rec->getAttribute('src') eq "$name.") ) {
      $aRecordIp = $rec->getAttribute('dst');
    }
  };
  return $aRecordIp;
}

sub addDomainDatabase {
  my ( $self, $dbId, $dbServerId, $domainId, $dbName, $dbType, $optionalPtr, $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent ) = @_;

  $self->regDatabaseObjectBackupPath( $self->getDomainsBackupPath( $domainId ), $dbId, $dbName, $dbServerId );
  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $node = $self->makeDatabaseNode( $dbId, $dbName, $dbType, 'domain', $domainId, $optionalPtr,
                                      $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent );
  my $parentGuid = $root->getAttribute('guid');
  $node->setAttribute( 'guid', $self->getDatabaseGuid( $parentGuid, $dbId ) ) if $parentGuid;
  $node->setAttribute( 'owner-guid', $parentGuid ) if $parentGuid;

  $root->getChild( 'databases', 1 )->addChild($node);
}

sub setDomainDefault {
  my ( $self, $domainId ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $addAsFirstElement = 1;
  $root->getChild( 'preferences', 1 )->addChild( XmlNode->new('default-domain'), $addAsFirstElement );
}

sub addDomainLimit {
  my ( $self, $domainId, $name, $value ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  $self->insertLimitNode( $root, $name, $value );
}

sub setDomainMailService {
  my ( $self, $domainId, $status, $ips ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mailServiceStatus = Status::make($status);

  my $rootMail = XmlNode->new('mailsystem');
  my $properties = $rootMail->getChild( 'properties', 1 );
  $properties->addChild($mailServiceStatus);

  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $properties->addChild($ipNode);
  }

  $root->addChild($rootMail);
}

sub addDomainCustomButton {
  my ( $self, $domainId, $id, $optionsPtr, $customButtonsDir, $icon ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $node = $self->makeCustomButtonNode( 'domains', $domainId, $id, $optionsPtr, $customButtonsDir, $icon );

  $root->getChild( 'preferences', 1 )->addChild( $node);
}

sub addToPreferences {
  my ( $self, $objectType, $objectId, $node ) = @_;

  my $parent;
  if ($objectType eq 'domain') {
    $parent = $self->getCashedDomainNode($objectId);
  }
  elsif($objectType eq 'client') {
    $parent = $self->{clientNodes}->{$objectId};
  }
  if ( !defined($parent) ) {
    Logging::warning('Error: addToPreferences: empty parent node', 'PackerStructure');
    return;
  }

  $parent->getChild( 'preferences', 1 )->addChild( $node);
}

sub setDomainCatchMail {
  my ( $self, $domainId, $catchAllAddr ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mailService = $root->getChild('mailsystem');
  unless (defined $mailService ) {
    Logging::warning("Error: setDomainCatchMail: there are no node 'mailsystem'", 'PackerStructure');
    return;
  }

  $mailService->getChild( 'preferences', 1 )->addChild(XmlNode->new( 'catch-all', 'content' => $catchAllAddr ) );
}

sub setDomainKeysDomainSupport {
  my ( $self, $domainId, $domainName, $state, $privateKeyPath, $publickKey ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mailService = $root->getChild('mailsystem');
  unless ( defined $mailService ) {
    Logging::warning("Error: setDomainKeysDomainSupport: there are no node 'mailsystem'", 'PackerStructure');
    return;
  }

  my $domainKeysNode = XmlNode->new( 'domain-keys',
    'attributes' => { 'state' => $state } );

  if ($state) {
    my $privateKey = $self->{content_transport}->addDomainContent( 'key', $domainId,
      "privatekey",
      'directory' => $privateKeyPath,
      'include'   => ['default']
    );

    $domainKeysNode->getChild( 'content', 1, 1 )->addChild( $privateKey ) if $privateKey;

    $domainKeysNode->setAttribute( 'public-key', $publickKey ) if ( defined($publickKey) && $publickKey );
  }

  $mailService->getChild( 'preferences', 1 )->addChild($domainKeysNode);
}

sub setDomainWebMail{
  my ( $self, $domainId, $webmail ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mailService = $root->getChild('mailsystem');
  if ( not $mailService ) {
    Logging::warning("Unable to dump domain webmail preferences, because of dump XML structure is not full", 'PackerStructure');
    return;
  }

  my $node = XmlNode->new( 'web-mail' );
  $node->setText( $webmail );
  $mailService->getChild( 'preferences', 1 )->addChild( $node );
}

sub setDomainGLSupport {
  my ( $self, $domainId, $state ) = @_;
  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mailsystem = $root->getChild('mailsystem');
  my $node = XmlNode->new( 'grey-listing' );
  $node->setText($state);
  $mailsystem->getChild('preferences', 1)->addChild($node);
}

sub setDomainDnsZone {
  my ( $self, $domainId, $paramsPtr, $recordsPtr ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  $self->makeDnsZone($root->getChild( 'properties', 1 ), $paramsPtr, $recordsPtr);
}

sub removeDomainDnsZone {
  my ( $self, $domainId ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $propertiesNode = $root->getChild( 'properties');
  if ( $propertiesNode ) {
    $propertiesNode->removeChildren('dns-zone');
  }
}

sub setDomainDnsOld {
  my ( $self, $domainId, $enabled, $type, $masterIp, $recordsPtr ) = @_;

  my @records = @{$recordsPtr};

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $properties = $root->getChild( 'properties', 1 );
  if ( !defined($properties) ) {
    Logging::warning("Error: setDomainDnsOld: empty 'properties' node", 'PackerStructure');
    return;
  }

  my $dnsNode = XmlNode->new('dns-zone');

  $dnsNode->setAttribute(
    'type',
    ( !$type || ( $type eq 'master' ) ) ? 'master' : 'slave'
  );

	$dnsNode->addChild(Status::make( $enabled eq 'true' ? 0 : 16 ));

  for my $hash (@records) {
    my $dnsrec = $self->makeDnsRecord($hash);
    if ($dnsrec) {
      $dnsNode->addChild($dnsrec);
    }
    $hash = undef;
  }

  if ( defined($masterIp) ) {
    $dnsNode->addChild( makeOldMasterRec($masterIp) );
  }

  $properties->addChild($dnsNode);
}

sub addDomainAlias {
  my ( $self, $domainId, $aliasPtr, $dnsParamsPtr, $dnsRecordsPtr) = @_;

  my %alias = %{$aliasPtr};

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $dnsSync = defined $alias{'dns'} ? $alias{'dns'} : 'false';
  
  my $aliasNode = XmlNode->new(
    'domain-alias',
    'attributes' => {
      'name' => $alias{'displayName'},
      'mail' => $alias{'mail'},
      'web'  => $alias{'web'},
      'dns'  => $dnsSync
    },
    'children' => [ Status::make( $alias{'status'} ) ]
  );

  $aliasNode->setAttribute( 'tomcat', $alias{'tomcat'} )
    if defined $alias{'tomcat'};

  if (PleskVersion::atLeast(11, 5, 23)) {
    $aliasNode->setAttribute('seo-redirect', defined $alias{'seoRedirect'} ? $alias{'seoRedirect'} : 'false');
  } else {
    $aliasNode->setAttribute('seo-redirect', 'false');
  }

  $aliasNode->setAttribute('external-id', $alias{'external_id'}) if defined $alias{'external_id'} and $alias{'external_id'} ne '';

  # DNS zone

  if ( $alias{'dns_zone_id'} != 0 ) {
    $self->makeDnsZone( $aliasNode, $dnsParamsPtr, $dnsRecordsPtr, $dnsSync );
  }

  my $addAsFirstElement = 1;
  $root->getChild( 'preferences', 1 )->addChild($aliasNode, $addAsFirstElement);
}

sub setDomainMailLists {
  my ( $self, $domainId, $status, $ips) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mlNode = XmlNode->new('maillists');

  my $propertiesNode = $mlNode->getChild( 'properties', 1 );
  $propertiesNode->addChild( Status::make( $status ) );
  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $propertiesNode->addChild($ipNode);
  }

  $root->addChild($mlNode);
}

sub addDomainMailList {
  my ($self, $domainId, $name, $passwd, $state, $ownersPtr, $membersPtr) = @_;

  my @owners = @{$ownersPtr};
  my %members = %{$membersPtr};

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @mailListServs = $root->getChildren('maillists');
  if ( scalar(@mailListServs) < 1 ) {
    Logging::warning('Unable to dump domain maillist, because of dump XML structure is not full', 'PackerStructure');
    return;
  }
  my $mailListService = $mailListServs[0];

  my $node = XmlNode->new(
    'maillist',
    'attributes' => { 'name', $name },
    'children'   => [ Status::make($state) ]
  );

  for my $listOwner (@owners) {
    $node->addChild( XmlNode->new( 'owner', 'content' => $listOwner ) );
  }

  $node->addChild( CommonPacker::makePasswordNode( $passwd, 'encrypted') );

  for my $memberEmail ( keys %members ) {
    my $member = XmlNode->new( 'recipient', 'content' => $memberEmail );
    if ( $members{$memberEmail} ne "" ) {
      $member->setAttribute( 'fullname', $members{$memberEmail} );
    }
    $node->addChild($member);
  }

  $mailListService->addChild($node);
}

sub setDomainTraffic {
  my ( $self, $domainId, $trafficValue) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  $root->addChild( XmlNode->new( 'traffic', 'content' => $trafficValue ) );
}

sub addDomainCertificate {
  my ( $self, $domainId, $name, $cert, $csr, $ca_cert, $pvt_key, $default) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $certNode = XmlNode->new('certificate');

  addUrlDecodedTextNode( $certNode, 'certificate-data', $cert ) if defined($cert);
  addUrlDecodedTextNode( $certNode, 'signing-request',  $csr ) if defined($csr);
  addUrlDecodedTextNode( $certNode, 'ca-certificate',   $ca_cert ) if defined($ca_cert);
  addUrlDecodedTextNode( $certNode, 'private-key', $pvt_key ) if defined($pvt_key);
  $certNode->setAttribute( 'name', $name );
  $certNode->setAttribute( 'default', 'true' ) if ( defined $default && $default == 1 );

  $root->getChild('certificates', 1 )->addChild($certNode);
}

sub setDomainTomcat {
  my ( $self, $domainId, $status, $ips) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $tomcatNode = XmlNode->new('tomcat');

  my $propertiesNode = $tomcatNode->getChild( 'properties', 1 );
  $propertiesNode->addChild( Status::make( $status ) );
  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $propertiesNode->addChild($ipNode);
  }
  $root->addChild($tomcatNode);
}

sub addDomainTomcatWebApp {
  my ( $self, $domainId, $domainName, $name, $status, $path) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @tomcatServs = $root->getChildren('tomcat');
  if ( scalar(@tomcatServs) < 1 ) {
    Logging::warning('Unable to dump domain Tomcat web-applications, because of dump XML structure is not full', 'PackerStructure');
    return;
  }
  my $tomcatService = $tomcatServs[0];

  my $item = XmlNode->new(
    'webapp',
    'attributes' => { 'name' => $name },
    'children'   => [ Status::make($status) ]
  );

  my $warFile = "$name.war";

  my $cid = $self->{content_transport}->addDomainContent( 'web-app', $domainId,
    "$name.webapp",
    "directory" => $path,
    "include"   => [$warFile]
  );
  $item->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;

  $tomcatService->addChild($item);
}

sub addDomainPersonalPermission {
  my ( $self, $domainId, $name, $value ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  if ( !defined($root) ) {
    Logging::warning('Unable to dump domain personal permissions, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  $self->makePermissionNode( $root->getChild( 'limits-and-permissions', 1 ), $name, $value );
}

sub setDomainPhostingEmpty {
  my ( $self, $domainId, $sysuserPtr, $ips ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phostingNode = XmlNode->new('phosting');

  my $preferencesNode = XmlNode->new('preferences');
  $phostingNode->addChild( $preferencesNode );

  if (defined $sysuserPtr) {
    $preferencesNode->addChild($self->makeSysUser($sysuserPtr));
  }
  
  my $propertiesNode = $phostingNode->getChild( 'properties', 1 );
  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $propertiesNode->addChild($ipNode);
  }

  $root->addChild($phostingNode);

  $self->regPhostingObjectBackupPath( $self->getDomainsBackupPath( $domainId ), $domainId );
  $self->{phostingNodes}->{$domainId} = $phostingNode;
}

sub setDomainPhosting {
  my ( $self, $domainId, $paramsPtr, $sysuserPtr, $scriptingPtr, $ips, $phpSettingsPtr ) = @_;

  my %params = %{$paramsPtr};
  my %scripting = %{$scriptingPtr};

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phostingNode = XmlNode->new('phosting');

  my $parentGuid = $root->getAttribute('guid');
  if ( $parentGuid ) {
    $phostingNode->setAttribute( 'guid', $self->getPhostingGuid( $parentGuid , $domainId ) );
    $phostingNode->setAttribute( 'owner-guid', $parentGuid );
  }

  ## %params ##
  my %hostingAttribute = (
    'https'   => 'ssl',
    'webstat' => 'webstat',
  );
  $phostingNode->setAttribute( 'shared-content', $params{'shared-content'} ) if defined($params{'shared-content'});
  while ( my ( $xmlName, $fieldName ) = each(%hostingAttribute) ) {
    # MySQL allows 'true', 'false' and '' in enums
    if ( defined( $params{$fieldName} )
      and $params{$fieldName} eq "true" )
    {
      $phostingNode->setAttribute( $xmlName, 'true' );
    }
  }

  if ( defined( $params{'webstat'} ) and $params{'webstat'} ) {
    $phostingNode->setAttribute( 'webstat', $params{'webstat'} );
  }

  if ( defined( $params{'https'} ) and $params{'https'} ) {
    $phostingNode->setAttribute( 'https', $params{'https'} );
  }

  if ( defined($params{'errdocs'} ) ) {
    $phostingNode->setAttribute( 'errdocs', 'true' );
  }

  if ( defined( $params{'www-root'} ) ) {
    $phostingNode->setAttribute( 'www-root', $params{'www-root'} );
  }

  if ( defined( $params{'cgi_bin_mode'} ) ) {
    $phostingNode->setAttribute( 'cgi_bin_mode', $params{'cgi_bin_mode'} );
  }

  if ( defined( $params{'sitebuilder-site-id'} ) ) {
    $phostingNode->setAttribute( 'sitebuilder-site-id', $params{'sitebuilder-site-id'} );
  }

  if ( defined( $params{'sitebuilder-site-published'} ) ) {
    $phostingNode->setAttribute( 'sitebuilder-site-published', $params{'sitebuilder-site-published'} );
  }

  if ( defined( $params{'wu_script'} ) ) {
    $phostingNode->setAttribute( 'wu_script', $params{'wu_script'} );
  }

  if ( defined( $params{'maintenance_mode'} ) ) {
    $phostingNode->setAttribute( 'maintenance-mode', $params{'maintenance_mode'} );
  }

  if ( defined( $params{'certificate_ref'} ) ) {
    $phostingNode->setAttribute( 'certificate', $params{'certificate_ref'} );
  }

  if ( defined( $params{'original-conf-directory'} ) ) {
    $phostingNode->setAttribute( 'original-conf-directory', $params{'original-conf-directory'} );
  }

  ## %sysuser ##
  my $prefs = $phostingNode->getChild( 'preferences', 1 );
  if (defined $sysuserPtr) {
    my $sysUserNode = $self->makeSysUser($sysuserPtr);
    $prefs->addChild($sysUserNode);
  }

  ## properties ##
  my $propertiesNode = $phostingNode->getChild( 'properties', 1);
  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $propertiesNode->addChild($ipNode);
  }

  ## %scripting ##
  my $scripringItem = XmlNode->new('scripting');
  while ( my ( $scriptXmlName, $value ) = each(%scripting) ) {
    $scripringItem->setAttribute( $scriptXmlName, $value ) if defined $value;
  }
  $phostingNode->getChild( 'limits-and-permissions', 1 )->addChild($scripringItem);

  if (defined $phpSettingsPtr and (scalar keys %{$phpSettingsPtr} != 0)) {
    $phostingNode->addChild($self->makePhpSettingsNode($phpSettingsPtr));
  }

  $root->addChild($phostingNode);

  $self->regPhostingObjectBackupPath( $self->getDomainsBackupPath( $domainId ), $domainId );
  $self->{phostingNodes}->{$domainId} = $phostingNode;

}

sub setDomainUserDataContent {
  my ( $self, $domainId, $domainName, $path, $optPtr) = @_;

  if (!$self->getContentTransport()->directoryExists("$path", 'remoteCommunicationIp' => $optPtr->{'remoteCommunicationIp'})) {
    Logging::debug("Domain $domainName. Skip backup of non-existent directory $path");
    return;
  }

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;
  
  my $phostings = $root->getChild('phosting');
  
  if (!$self->{migrationMode}) {
    my ($fhApache, $tmpApache) = File::Temp::tempfile( AgentConfig::getBackupTmpDir() . "/incApacheFilesXXXXXX", UNLINK => 1 );
    my ($fhUser, $tmpUserData) = File::Temp::tempfile( AgentConfig::getBackupTmpDir() . "/incUserDataXXXXXX", UNLINK => 1 );
    
    chown getpwnam("root"), getgrnam("root"), $tmpApache, $tmpUserData;
    chmod 0600, $tmpApache, $tmpUserData;
    
    my %excludes = map { $_ => 1 } @{$optPtr->{'exclude'}};
    Logging::debug("List files in " . $path);
    $self->_listApacheAndUserFilesInFolder($path, $fhUser, $fhApache, \%excludes);

    close $fhUser;
    close $fhApache;

    my $userDataSize = $self->_setDomainContentIncrementally($domainId, $path, $optPtr, $tmpUserData, $phostings, 'user-data', $optPtr->{'sysuser'});
    if ($userDataSize != undef) {
      my $apacheSize = $self->_setDomainContentIncrementally($domainId, $path, $optPtr, $tmpApache, $phostings, 'apache-files', $self->{apache_user}{'user'});
      if ($apacheSize != undef) {
        $self->{stat}{vhostDumpsCount}++;
        $self->{stat}{vhostSizeDumped} += $userDataSize + $apacheSize;
        return;
      }
    }
  }

  my @apacheFiles;
  my $cmd = "find $path \\( \\( -type d -user $self->{apache_user}{user} -group $self->{apache_user}{group} ! -perm -o+rw -prune \\) -o \\( ! -type d -user $self->{apache_user}{user} -group $self->{apache_user}{group} ! -perm -o+rw \\) \\) -printf \"%P\n\"";
  my $findCmd = `$cmd`;
  if ($? eq '0') {
    @apacheFiles = split "\n", $findCmd;
  }

  my @exclude = (@apacheFiles) ? (@apacheFiles, @{$optPtr->{'exclude'}}) : @{$optPtr->{'exclude'}};
  my $cidUserData = $self->{content_transport}->addDomainContent( 'user-data', $domainId,
    'user-data',
    'directory' => $path,
    'sysuser' => $optPtr->{'sysuser'},
    'exclude' => \@exclude,
    'remoteCommunicationIp' => $optPtr->{'remoteCommunicationIp'}
  );
  $phostings->getChild( 'content', 1, 1 )->addChild( $cidUserData ) if $cidUserData;
  @{$self->{domainVhost}->{$domainId}} = ( 'user-data', $cidUserData, $path )  if $cidUserData;

  my $cidApache;
  if (@apacheFiles) {
    $cidApache = $self->{content_transport}->addDomainContent( 'apache-files', $domainId,
      'apache-files',
      'directory' => $path,
      'sysuser' => $self->{apache_user}{'user'},
      'include' => \@apacheFiles,
      'remoteCommunicationIp' => $optPtr->{'remoteCommunicationIp'}
    );
    $phostings->getChild( 'content', 1, 1 )->addChild( $cidApache ) if $cidApache;
    @{$self->{domainVhost}->{$domainId}} = ( 'apache-files', $cidApache, $path )  if $cidApache;
  }
  
  $self->{stat}{vhostDumpsCount}++;
  $self->{stat}{vhostSizeDumped} +=  $self->_getContentSizeFromCidNode($cidUserData) + $self->_getContentSizeFromCidNode($cidApache);
}

sub _setDomainContentIncrementally {
  my ($self, $domainId, $srcDirPath, $optPtr, $listingFile, $phostings, $cidType, $sysuser) = @_;
  
  my $result = undef;
  
  my $changesFile = HelpFuncs::mktemp(AgentConfig::getBackupTmpDir() . "/chnXXXXXXXX");
  my $indexFile = HelpFuncs::mktemp(AgentConfig::getBackupTmpDir() . "/idxXXXXXXXX");
  my $dependenciesFile = HelpFuncs::mktemp(AgentConfig::getBackupTmpDir() . "/depXXXXXXXX");
  
  my $cmd = [
    AgentConfig::pmmRasBin(), '--get-diff',
    '--listing-file', $listingFile,
    '--backup-file', $self->{storage}->{output_dir}."/".$self->getDomainsBackupPath($domainId, $cidType).".tgz",
    '--changes-file', $changesFile,
    '--index-file', $indexFile,
    '--dependencies-file', $dependenciesFile
  ];
  
  if ($self->{lastIndexPath}) {
    push (@{$cmd}, '--last-index-path');
    push (@{$cmd}, $self->{lastIndexPath});
  }

  if (defined($self->{storage}->{ftpsettings})) {
    push (@{$cmd}, '--use-ftp-passive-mode') if $self->{storage}->{passive_mode};
    $ENV{'DUMP_STORAGE_PASSWD'} = $self->{storage}->{ftpsettings}->{password};
  }

  Logging::debug("Execute: ".join(' ', @{$cmd}));

  my($err);
  run $cmd, '>&', \$err;
  
  if ($?) {
    Logging::debug("Unable to calculate file diff, full dump will be performed instead. Error (".$?.") ".$err, "IncrementalBackup");
  } else {
    my $dumpXmlRelativePath = $self->getDomainsBackupPath($domainId, 'info', 0);
    my $posNameBegin = rindex( $dumpXmlRelativePath, '/' ) + 1;
    my $dumpName = substr($dumpXmlRelativePath, $posNameBegin);
    my $dumpDirPath = ($posNameBegin > 0) ? substr($dumpXmlRelativePath, 0, $posNameBegin - 1) : "";
    $self->{storage}->moveFileToDiscovered($indexFile, "cid_".$cidType, $dumpDirPath, $dumpName);

    my $cid_content = $self->{content_transport}->addDomainContent( $cidType, $domainId,
      $cidType,
      'directory' => $srcDirPath,
      'sysuser' => $sysuser,
      'includes-file' => $changesFile,
      'exclude' => \@{$optPtr->{'exclude'}},
      'remoteCommunicationIp' => $optPtr->{'remoteCommunicationIp'},
      'no_recursion' => 1,
      'indexed' => 1
    );
    $phostings->getChild( 'content', 1, 1 )->addChild( $cid_content ) if $cid_content;
    @{$self->{domainVhost}->{$domainId}} = ( $cidType, $cid_content, $srcDirPath )  if $cid_content;
    
    $result = $self->_getContentSizeFromCidNode($cid_content);
    $self->_keepRelatedDumps($dumpDirPath, $dependenciesFile);
  }

  unlink($changesFile) if -e $changesFile;
  unlink($indexFile) if -e $indexFile;
  unlink($dependenciesFile) if -e $dependenciesFile;
  
  return $result;
}

sub _getContentSizeFromCidNode {
  my ($self, $cidNode) = @_;
  
  my $size = 0;
  if ($cidNode) {
    my $contentFileNodes = $cidNode->getChildren('content-file');
    foreach my $contentFileNode ( $cidNode->getChildren('content-file') ) {
      if ($contentFileNode) {
        $size += $contentFileNode->getAttribute('size');
      }
    }
  }
  
  return $size;
}

sub _keepRelatedDumps {
  my ($self, $pathToDumpFolder, $relatedDumpsFile) = @_;

  my @relatedDumps = HelpFuncs::loadArrayFromFile($relatedDumpsFile);
  return unless (@relatedDumps);
  
  @relatedDumps = sort(@relatedDumps);  
  $self->_keepRelatedDumpsTo($pathToDumpFolder, \@relatedDumps);
}

sub _keepRelatedDumpsTo {
  my ($self, $pathToDumpFolder, $relatedDumps) = @_;

  if (exists $self->{relatedDumps}->{$pathToDumpFolder}) {
    my @diff = HelpFuncs::arrayDifference($relatedDumps, $self->{relatedDumps}->{$pathToDumpFolder});
    my @merged = (@{$self->{relatedDumps}->{$pathToDumpFolder}}, @diff);
    @{$self->{relatedDumps}->{$pathToDumpFolder}} = sort(@merged);
  } else {
    @{$self->{relatedDumps}->{$pathToDumpFolder}} = @{$relatedDumps};
  }

  return if ($pathToDumpFolder eq '');

  my $pathToParentDumpFolder = dirname(dirname($pathToDumpFolder));
  $pathToParentDumpFolder = "" if ($pathToParentDumpFolder eq '.');

  $self->_keepRelatedDumpsTo($pathToParentDumpFolder, $relatedDumps);
}

sub _listApacheAndUserFilesInFolder {
  my ($self, $srcDirPath, $outUserFiles, $outApacheFiles, $excludes, $isApacheBranch, $szPathTrim, $usersCache, $groupsCache) = @_;

  return if !opendir(my $dh, $srcDirPath);
  
  $szPathTrim = length($srcDirPath) + 1 if (not defined $szPathTrim or $szPathTrim == 0);

  my @dir = readdir($dh);
  closedir($dh);
  @dir = sort(@dir);
  
  my $entry;
  foreach $entry (@dir) {
    next if ($entry eq "." || $entry eq "..");
    my $entryFullPath = $srcDirPath."/".$entry;
    my $entryRelPath = substr $entryFullPath, $szPathTrim;
    
    next if (exists($excludes->{$entryRelPath}));
    
    my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = lstat($entryFullPath) or next;
    
    my $addToApacheListing = ($isApacheBranch || ($uid == $self->{apache_user}{uid} && $gid == $self->{apache_user}{gid} && !($mode & Fcntl::S_IROTH && $mode & Fcntl::S_IWOTH)));
    my $ftype = (Fcntl::S_ISDIR($mode) && !Fcntl::S_ISLNK($mode)) ? 'd' : 'f';
    my $fsize = ($ftype eq 'd') ? "0" : "$size";
    $mode = sprintf '%04o', $mode & 07777;
    $mtime = HelpFuncs::Max($mtime, $ctime);

    $usersCache->{$uid} = getpwuid($uid) if (not exists $usersCache->{$uid});
    $groupsCache->{$gid} = getgrgid($gid) if (not exists $groupsCache->{$gid});
    
    my $outFile = ($addToApacheListing) ? $outApacheFiles : $outUserFiles;
    print $outFile $entryRelPath."\t".$ftype."\t".$fsize."\t".$mtime."\t".$usersCache->{$uid}."\t".$groupsCache->{$gid}."\t".$mode."\n";
    
    if ($ftype eq 'd') {
      $self->_listApacheAndUserFilesInFolder($entryFullPath, $outUserFiles, $outApacheFiles, $excludes, $addToApacheListing, $szPathTrim, $usersCache, $groupsCache);
    }
  }
}

sub setDomainPhostingFullContent {
  my ( $self, $domainId, $domainName, $path, $optPtr) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phostings = $root->getChild('phosting');
  my $cid_content = $self->{content_transport}->addDomainContent( 'vhost', $domainId,
    'vhost',
    'directory' => $path,
    'sysuser' => $optPtr->{'sysuser'},
    'exclude' => $optPtr->{'exclude'},
    'remoteCommunicationIp' => $optPtr->{'remoteCommunicationIp'}
  );
  $phostings->getChild( 'content', 1, 1 )->addChild( $cid_content ) if $cid_content;
  @{$self->{domainVhost}->{$domainId}} = ( 'vhost', $cid_content, $path )  if $cid_content;
}

sub setDomainMailNamesContent{
  my ( $self, $domainId, $domainAsciiName, $path, $sysuser, $remoteMailIp ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my %options;
  $options{'directory'} = $path;
  $options{'sysuser'} = $sysuser;

  my $currentTransportOs = $self->{content_transport_os};

  if (defined $remoteMailIp) {
    $options{'remoteCommunicationIp'} = $remoteMailIp;

    my $isWin = PleskStructure::getServiceNodeProperty(PleskStructure::getRemoteNodeId($remoteMailIp), "OS_WIN") eq "true";

    if ( $isWin ) {
      $self->setContentTransport($self->getContentTransportType(), 'windows');
    } else {
      $self->setContentTransport($self->getContentTransportType(), 'unix');
    }
  }

  my $mailContent = $self->{content_transport}->addDomainContent( 'domainmail', $domainId,
      'mn',
      %options
  );
  
  $self->{stat}{mailSizeDumped} += $self->_getContentSizeFromCidNode($mailContent);
  $self->{stat}{mailDumpsCount}++;

  if (defined $remoteMailIp) {
    $self->setContentTransport($self->getContentTransportType(), $currentTransportOs);
  }

  $root->getChild( 'mailsystem' )->getChild( 'content', 1, 1 )->addChild( $mailContent ) if $mailContent;
  @{$self->{domainMailContent}->{$domainId}} = ( 'domainmail', $mailContent, $path ) if $mailContent;
}

sub setDomainMailListContent{
  my ( $self, $domainId, $domainAsciiName, $path, $optPtr ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $mailContent = $self->{content_transport}->addDomainContent( 'domainmaillist', $domainId,
      'ml',
      'directory' => $path,
      'include'   => $optPtr->{'include'},
      'follow_symlinks' => $optPtr->{'follow_symlinks'},
      'sysuser' => $optPtr->{'sysuser'}
  );

  $root->getChild( 'maillists' )->getChild( 'content', 1, 1 )->addChild( $mailContent ) if $mailContent;
  @{$self->{domainMailListContent}->{$domainId}} = ( 'domainmaillist', $mailContent, $path ) if $mailContent;
}

sub setDomainPhostingContentCommon {
  my ( $self, $domainId, $domainName, $path, $contentType ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Unable to dump domain phosting content, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $cid_content = $self->{content_transport}->addDomainContent( $contentType, $domainId,
    $contentType,
    'directory' => $path
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_content ) if $cid_content;
  return $cid_content;
}

sub setDomainPhostingProtectedDirContent {
  my ( $self, $domainId, $domainName, $path ) = @_;
  $self->setDomainPhostingContentCommon($domainId, $domainName, $path, 'pd');
}

sub setDomainPhostingStatisticsContent {
  my ( $self, $domainId, $domainName, $path, $optPtr ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Unable to dump domain phosting statistics, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $cid_content = $self->{content_transport}->addDomainContent( 'statistics', $domainId,
    'statistics',
    'directory' => $path,
    'remoteCommunicationIp' => $optPtr->{'remoteCommunicationIp'},
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_content ) if $cid_content;
  @{$self->{domainStatistics}->{$domainId}} = ( 'statistics', $cid_content, $path )  if $cid_content;
}

sub setDomainPhostingWebstatContent {
  my ( $self, $domainId, $domainName, $path ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Unable to dump domain phosting webstat content, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $cid_webstat = $self->{content_transport}->addDomainContent( 'webstat', $domainId,
    'webstat',
    'directory' => $path
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_webstat ) if $cid_webstat;
}

sub setDomainPhostingWebstatSslContent {
  my ( $self, $domainId, $domainName, $path ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Error: setDomainPhostingWebstatSslContent: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $cid_webstat_ssl = $self->{content_transport}->addDomainContent( 'webstat_ssl', $domainId,
    'webstat-ssl',
    'directory' => $path
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_webstat_ssl ) if $cid_webstat_ssl;
}

sub setDomainPhostingFtpstatContent {
  my ( $self, $domainId, $domainName, $path ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Unable to dump domain phosting ftpstat content, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $cid_ftpstat = $self->{content_transport}->addDomainContent( 'ftp_stat', $domainId,
    'ftpstat',
    'directory' => $path
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_ftpstat ) if $cid_ftpstat;
}

sub setDomainPhostingLogsContent {
  my ( $self, $domainId, $domainName, $path ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Error: setDomainPhostingLogsContent: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $cid_logs =  $self->{content_transport}->addDomainContent( 'logs', $domainId,
    'logs',
    'directory' => $path
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_logs ) if $cid_logs;
}

sub setDomainPhostingAnonFtpstatContent {
  my ( $self, $domainId, $domainName, $path ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Error: setDomainPhostingAnonFtpstatContent: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $cid_anon_ftpstat = $self->{content_transport}->addDomainContent( 'anon_ftpstat', $domainId,
    'anon-ftpstat',
    'directory' => $path
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_anon_ftpstat ) if $cid_anon_ftpstat;
}

sub setDomainPhostingConfContent {
  my ( $self, $domainId, $domainName, $path, $optPtr ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my @phostings = $root->getChildren('phosting');
  if ( scalar(@phostings) < 1 ) {
    Logging::warning('Error: setDomainPhostingConfContent: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $cid_conf;
  $cid_conf = $self->{content_transport}->addDomainContent( 'conf', $domainId,
    'conf',
    'directory' => $path,
    'exclude' => $optPtr->{'exclude'}
  );
  $phostings[0]->getChild( 'content', 1, 1 )->addChild( $cid_conf ) if $cid_conf;
}

sub setDomainLogrotation {
  my ( $self, $domainId, $logRotationPtr) = @_;

  my %logRotation = %{$logRotationPtr};

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phosting = $root->getChild('phosting');
  if ( not $phosting ) {
    Logging::warning('Unable to dump domain log rotation settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $node;
  if (keys %logRotation) {
    $self->makeLogrotationNode( $phosting->getChild( 'preferences', 1 ), \%logRotation );
  }
}

sub setDomainAnonFtp {
  my ( $self, $domainId, $domainName, $propsPtr, $pub_path, $incoming_path, $optPtr ) = @_;

  my %props = %{$propsPtr};

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phosting = $root->getChild('phosting');
  if ( not $phosting ) {
    Logging::warning('Error: setDomainAnonFtp: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $anonRoot = XmlNode->new('anonftp');
  if ( $props{'status'} =~ /true/ ) {
    $anonRoot->setAttribute( 'pub', 'true' );
  }
  if ( $props{'incoming'} =~ /true/ ) {
    $anonRoot->setAttribute( 'incoming', 'true' );
  }
  if ( defined( $props{'max_conn'} ) ) {
    makeAnonftpLimitNode( $anonRoot, 'max-connections',
      $props{'max_conn'} );
  }
  if ( defined( $props{'bandwidth'} ) ) {
    makeAnonftpLimitNode( $anonRoot, 'bandwidth',
      $props{'bandwidth'} );
  }
  if ( defined( $props{'quota'} ) ) {
    makeAnonftpLimitNode( $anonRoot, 'incoming-disk-quota',
      $props{'quota'} );
  }
  if ( defined( $props{'display_login'} ) ) {
    $anonRoot->setAttribute( 'display-login', $props{'display_login'} );
  }
  if ( $props{'incoming_readable'} =~ /true/ ) {
    makeAnonftpPermissionNode( $anonRoot, 'incoming-download' );
  }
  if ( $props{'incoming_subdirs'} =~ /true/ ) {
    makeAnonftpPermissionNode( $anonRoot, 'incoming-mkdir' );
  }
  if ( defined( $props{'login_text'} ) ) {
    my $loginMessageNode =
      XmlNode->new( 'login-message',
      'content' => $props{'login_text'} );
    $anonRoot->addChild($loginMessageNode);
  }

  $phosting->getChild( 'preferences', 1 )->addChild($anonRoot);
}

sub addDomainProtectedDir {
  my ( $self, $domainId, $pdirPath, $pdirTitle, $pdirNonSSL, $pdirSSL, $pdirCGI, $pdirUsersPtr) = @_;

  my @pdirUsers = @{$pdirUsersPtr};

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless ( defined $phostingNode ) {
    Logging::warning('Error: addDomainProtectedDir: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $pdirNode = XmlNode->new('pdir');

  # workaround of CLI inabliity to create '' directory.
  if ( $pdirPath eq '' ) {
    $pdirPath = '/';
  }

  $pdirNode->setAttribute( 'name', $pdirPath );
  if ($pdirTitle) {
    $pdirNode->setAttribute( 'title', $pdirTitle );
  }
  if ($pdirNonSSL) {
    $pdirNode->setAttribute( 'nonssl', $pdirNonSSL );
  }
  if ($pdirSSL) {
    $pdirNode->setAttribute( 'ssl', $pdirSSL );
  }
  if ($pdirCGI) {
    $pdirNode->setAttribute( 'cgi', $pdirCGI );
  }

  my $userNode;
  my $item;
  for my $user (@pdirUsers) {
    $userNode = XmlNode->new('pduser');
    $pdirNode->addChild($userNode);
    $userNode->setAttribute( 'name', $user->{'login'} );

    $item = CommonPacker::makePasswordNode( $user->{'passwd'}, CommonPacker::normalizePasswordType( $user->{'passwdType'} ) );
    $userNode->addChild($item);
  }

  $phostingNode->getChild( 'preferences', 1 )->addChild($pdirNode);
}

sub addDomainSubFtpUser {
  my ( $self, $domainId, $domainName, $sysuserPtr, $ptrFtpUser) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phostingNode = $root->getChild('phosting');

  my $ftpuserNode = XmlNode->new('ftpuser');
  $ftpuserNode->setAttribute('name', $sysuserPtr->{'login'});
  $ftpuserNode->setAttribute('external-id', $ptrFtpUser->{'external_id'}) if defined $ptrFtpUser->{'external_id'} and $ptrFtpUser->{'external_id'} ne '';
  $ftpuserNode->addChild($self->makeSysUser($sysuserPtr));
  $phostingNode->getChild( 'ftpusers', 1 )->addChild($ftpuserNode);
}

sub setEmptySubFtpUsersNode {
  my ($self, $domainId) = @_;
  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $phostingNode = $root->getChild('phosting', 1);

  $phostingNode->getChild('ftpusers', 1);
}

sub removeResellerChildNodeIfEmpty {
  my ( $self, $clientId, $nodeName ) = @_;
  my $root = $self->getCashedClientNode( $clientId );
  return unless defined $root;

  my $containerNode = $root->getChild( $nodeName );
  if ( defined $containerNode && not $containerNode->getChildren() ) {
    $root->removeChildren( $nodeName );
  }
}

sub removePhostingChildNodeIfEmpty {
  my ( $self, $domainId, $nodeName ) = @_;
  my $root = $self->getCashedDomainNode( $domainId );
  return unless defined $root;

  my $phostingNode = $root->getChild( 'phosting', 1 );

  my $containerNode = $phostingNode->getChild( $nodeName . 's' );
  if ( defined $containerNode && not $containerNode->getChildren( $nodeName ) ) {
    $phostingNode->removeChildren( $nodeName . 's' );
  }
}

sub removeMailSystemChildNodeIfEmpty {
  my ( $self, $domainId, $nodeName ) = @_;
  my $root = $self->getCashedDomainNode( $domainId );
  return unless defined $root;

  my $mailsystemNode = $root->getChild( 'mailsystem' );

  my $containerNode = $mailsystemNode->getChild( $nodeName . 's' );
  if ( defined $containerNode && not $containerNode->getChildren( $nodeName ) ) {
    $mailsystemNode->removeChildren( $nodeName . 's' );
  }
}

sub addDomainWebUser {
  my ( $self, $domainId, $domainName, $sysuserPtr, $scriptingPtr, $webUserHome, $privatePath ) = @_;

  my %sysuser = %{$sysuserPtr};
  my %scripting = %{$scriptingPtr};

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless( defined $phostingNode ) {
    Logging::warning('Unable to dump domain webuser preferences, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $webuserNode = XmlNode->new('webuser');

  $webuserNode->addChild($self->makeSysUser(\%sysuser));

  my $userName = $sysuser{'login'};
  $webuserNode->setAttribute( 'name', $userName );

  my %webUserScripting = (
    'ssi'         => 'ssi',
    'php'         => 'php',
    'cgi'         => 'cgi',
    'perl'        => 'perl',
    'asp'         => 'asp',
    'python'      => 'python',
    'asp_dot_net' => 'asp_dot_net',
    'fastcgi'     => 'fastcgi',
    'miva'        => 'miva'
  );

  my $item = XmlNode->new('scripting');
  while ( my ( $xmlName, $fieldName ) = each(%webUserScripting) ) {
    if ( defined $scripting{$fieldName}
      and ( $scripting{$fieldName} eq 'true' or $scripting{$fieldName} eq 'false' ) )
    {
      $item->setAttribute( $xmlName, $scripting{$fieldName} );
    }
  }

  if (exists $scripting{'write_modify'} && defined $scripting{'write_modify'}) {
    $item->setAttribute('write_modify', $scripting{'write_modify'}) ;
  }

  $webuserNode->addChild($item);

  $phostingNode->getChild( 'webusers', 1 )->addChild($webuserNode);
}

sub setDomainWebalizer {
  my ( $self, $domainId, $directRef, $hiddenRefsPtr, $groupRefsPtr) = @_;

  my @hiddenRefs = @{$hiddenRefsPtr};
  my @groupRefs = @{$groupRefsPtr};

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless( defined $phostingNode ) {
    Logging::warning('Unable to dump domain webalizer settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $webalizerNode = XmlNode->new("webalizer");

  if ($directRef)
  {
    $webalizerNode->setAttribute( 'hide-direct-referrer', 'true' );
  }

  for my $ref (@hiddenRefs) {
    $webalizerNode->addChild(
      XmlNode->new(
        "webalizer-hidden-referrer", 'content' => $ref
      )
    );
  }

  for my $groupRef (@groupRefs ) {
    $webalizerNode->addChild(
      XmlNode->new(
        'webalizer-group-referrer',
        'content'    => $groupRef->{'ref'},
        'attributes' => { 'group-name' => $groupRef->{'name'} }
      )
    );
  }

  $phostingNode->getChild( 'preferences', 1 )->addChild($webalizerNode);
}

sub makeSiteAppInstalled{
  my ( $self, $sappId, $prefix, $isSsl, $apsRegistryId ) = @_;

  my $sapp_installed = $self->{sappNodes}->{$sappId};

  # sapp-installdir
  my $sapp_installdir =
    XmlNode->new( 'sapp-installdir',
    'children' => [ XmlNode->new( 'sapp-prefix', 'content' => $prefix ) ] );
  if ( $isSsl ) {
    $sapp_installdir->addChild( XmlNode->new('sapp-ssl') );
  }
  if ($apsRegistryId) {
    $sapp_installdir->addChild( XmlNode->new( 'aps-registry-id', 'content' => $apsRegistryId ));
  }
  $sapp_installed->addChild($sapp_installdir);
}

sub setNonApsWordpressInstance {
  my ( $self, $domainId, $wpInstance, $wpInstancePropertiesPtr, $putToPhostingNode) = @_;

  my @wpInstanceProperties = @{$wpInstancePropertiesPtr};
  my $domainNode = $self->getCashedDomainNode($domainId);
  return unless defined $domainNode;

  my $wpInstancesNode;
  if ($putToPhostingNode eq 'true') {
    my $phostingNode = $domainNode->getChild('phosting');
    unless( defined $phostingNode ) {
      Logging::warning('Error: setNonApsWordpressInstance: there are no node "phosting"', 'PackerStructure');
      return;
    }

    $wpInstancesNode = $phostingNode->getChild('wordpress-instances', 1);
  } else {
    $wpInstancesNode = $domainNode->getChild('wordpress-instances', 1);
  }

  my $wpInstanceNode = XmlNode->new("wordpress-instance");
  $wpInstanceNode->setAttribute( 'path', $wpInstance->{'path'} );
  $wpInstanceNode->setAttribute( 'is-ignored', $wpInstance->{'isIgnored'} );
  if ( @wpInstanceProperties ) {
    foreach my $property (@wpInstanceProperties) {
      $wpInstanceNode->addChild(
        XmlNode->new(
          'property',
          'children' => [
            XmlNode->new(
              'name', 'content' => $property->{'name'}
            ),
            XmlNode->new(
              'value', 'content' => $property->{'value'}
            )
          ]
        )
      );
    }
  }
  $wpInstancesNode->addChild($wpInstanceNode);
}

sub setComposerInstances {
  my ($self, $domainId, $embeddedInfo) = @_;

  return unless '' ne $embeddedInfo && defined($embeddedInfo->{'composer-instances'});

  my $domainNode = $self->getCashedDomainNode($domainId);
  return unless defined $domainNode;

  my ($composerInstances) = @{$embeddedInfo->{'composer-instances'}};
  eval {require XML::Simple; 1;};
  my $xs = XML::Simple->new(ForceArray => 1);
  my $composerInstancesXml = $xs->XMLout($composerInstances, RootName => 'composer-instances');
  $composerInstancesXml = Encode::encode('UTF-8', $composerInstancesXml); # workaround for UTF8 symbols
  $domainNode->addChild(XmlNode->new(undef, 'raw' => $composerInstancesXml));
}

sub setDomainPerfomance {
  my ( $self, $domainId, $max_connection, $traffic_bandwidth) = @_;

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless ( defined $phostingNode ) {
    Logging::warning('Unable to dump domain performance settings, because of dump XML structure is not full', 'PackerStructure');
    return;
  }

  my $perfomanceNode = XmlNode->new('perfomance');
  $perfomanceNode->addChild(
    XmlNode->new(
      'max-connections', 'content' => $max_connection
    )
  );
  $perfomanceNode->addChild(
    XmlNode->new(
      'traffic-bandwidth', 'content' => $traffic_bandwidth
    )
  );

  $phostingNode->getChild( 'preferences', 1 )->addChild( $perfomanceNode );
}

sub appendNodeToPhosting {
  my ( $self, $domainId, $rawXml ) = @_;

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless ( defined $phostingNode ) {
    Logging::warning('Error: appendNodeToPhosting: there are no node "phosting"', 'PackerStructure');
    return;
  }

  $phostingNode->addChild(XmlNode->new(undef, 'raw' => $rawXml));
}

sub setIisAppPool {
  my ( $self, $domainId, $info ) = @_;

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless ( defined $phostingNode ) {
    Logging::warning('Error: setIisAppPool: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $phostingPreferencesNode = $phostingNode->getChild('preferences');
  unless ( defined $phostingPreferencesNode ) {
    Logging::warning('Error: setIisAppPool: there are no node "phosting/preferences"', 'PackerStructure');
    return;
  }

  $phostingPreferencesNode->addChild(XmlNode->new(undef, 'raw' => $info));
}


sub setDomainWebStat{
  my ( $self, $domainId, $stat_ttl) = @_;

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless( defined $phostingNode ) {
    Logging::warning('Error: setDomainWebStat: there are no node "phosting"', 'PackerStructure');
    return;
  }
  my $webstatNode = XmlNode->new('web-stat');
  $webstatNode->setAttribute( 'stat-ttl', $stat_ttl ) if $stat_ttl;
  $phostingNode->getChild( 'preferences', 1 )->addChild( $webstatNode );
}

sub setDomainShosting {
  my ( $self, $domainId, $forward, $ips) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $shostingNode = XmlNode->new( 'shosting');
  $shostingNode->addChild(XmlNode->new('url', 'content' => $forward->{'redirect'}));

  my $propertiesNode = $shostingNode->getChild('properties', 1);

  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $propertiesNode->addChild($ipNode);
  }

  if (defined ($forward->{'http_code'})) {
	my $httpcodeNode = XmlNode->new('httpcode', 'content' => $forward->{'http_code'});
	$shostingNode->addChild($httpcodeNode);
  }

  $root->addChild($shostingNode);
}

sub setDomainFhosting {
  my ( $self, $domainId, $forward, $ips) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $fhostingNode = XmlNode->new( 'fhosting');
  $fhostingNode->addChild(XmlNode->new('url', 'content' => $forward));

  my $propertiesNode = $fhostingNode->getChild('properties', 1);

  foreach my $ip (keys %{$ips}) {
    my $ipNode = $self->makeIpNode($ip, $ips->{$ip});
    $propertiesNode->addChild($ipNode);
  }

  $root->addChild($fhostingNode);
}

sub addSite {
  my ( $self, $domainId, $sitePtr) = @_;

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phostingNode = $parent->getChild('phosting');
  unless ( defined $phostingNode ) {
    Logging::warning('Error: addSite: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $siteNode = XmlNode->new( 'site' );

  $phostingNode->getChild( 'sites', 1 )->addChild($siteNode);

}

###### SubDomain functions ######

sub addSubDomain {
  my ( $self, $domainId, $ptrSubDomain, $subDomainName, $subAsciiDomainName, $https, $shared_content, $sysuserPtr, $scriptingPtr) = @_;
  $subAsciiDomainName = $subDomainName if not $subAsciiDomainName;
  my $subDomainId = $ptrSubDomain->{'id'};
  $self->regSubdomainObjectBackupPath( $self->getPhostingBackupPath( $domainId ), $subDomainId, $subAsciiDomainName, $domainId );

  my %sysuser = %{$sysuserPtr};
  my %scripting = %{$scriptingPtr};

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phosting = $parent->getChild('phosting');
  if ( not $phosting ) {
    Logging::warning('Error: addSubdomain: there are no node "phosting"', 'PackerStructure');
    return;
  }

  my $root = XmlNode->new('subdomain', 'attributes' => {'id' => $subDomainId});
  $self->{subDomainNodes}->{$subDomainId} = $root;
  my $phostingGuid = $phosting->getAttribute('guid');
  $root->setAttribute( 'owner-guid', $phostingGuid ) if $phostingGuid;

  $root->setAttribute( 'name', $subDomainName );

  if ( 'true' eq $https ) {
    $root->setAttribute( 'https', 'true' );
  }

  if ( $shared_content ne '' ) {
    $root->setAttribute( 'shared-content', $shared_content );
  }

  if ( exists $ptrSubDomain->{'www-root'} ) {
    $root->setAttribute( 'www-root', $ptrSubDomain->{'www-root'} );
  }

  if ( exists $ptrSubDomain->{'maintenance_mode'} ) {
    $root->setAttribute( 'maintenance-mode', $ptrSubDomain->{'maintenance_mode'});
  }

  if ( exists $ptrSubDomain->{'certificate_ref'} ) {
    $root->setAttribute( 'certificate', $ptrSubDomain->{'certificate_ref'});
  }

  ## %sysuser ##

  if (%sysuser)
  {
    $root->addChild($self->makeSysUser(\%sysuser));
  }

  ## %scripting ##

  my $scripringItem = XmlNode->new('scripting');
  while ( my ( $xmlName, $value ) = each(%scripting) ) {
      $scripringItem->setAttribute( $xmlName, $value );
  }
  $root->addChild($scripringItem);

  $phosting->getChild( 'subdomains', 1 )->addChild($root);

}

sub setSubDomainHttpdocsContent {
  my ( $self, $subDomainId, $path, $exclude_httpdocs_files_ptr, $webspaceId ) = @_;

  my @exclude_httpdocs_files = @{$exclude_httpdocs_files_ptr};

  my $root = $self->{subDomainNodes}->{$subDomainId};

  my $cid_docroot = $self->{content_transport}->addSubDomainPhostingContent( 'docroot', $subDomainId, $webspaceId,
    "httpdocs",
    "directory" => $path,
    "exclude"   => \@exclude_httpdocs_files
  );
  $root->getChild( 'content', 1, 1 )->addChild( $cid_docroot ) if $cid_docroot;
}

sub setSubDomainHttpsdocsContent {
  my ( $self, $subDomainId, $path, $exclude_httpsdocs_files_ptr ) = @_;

  my @exclude_httpsdocs_files = @{$exclude_httpsdocs_files_ptr};

  my $root = $self->{subDomainNodes}->{$subDomainId};

  my $cid_docroot_ssl = $self->{content_transport}->addSubDomainPhostingContent('docroot_ssl', $subDomainId, undef,
    "httpsdocs",
    "directory" => $path,
    "exclude"   => \@exclude_httpsdocs_files
  );
  $root->getChild( 'content', 1, 1 )->addChild( $cid_docroot_ssl ) if $cid_docroot_ssl;
}

sub setSubDomainErrorDocsContent {
  my ( $self, $subDomainId, $path ) = @_;
  my $subdomainNode = $self->{subDomainNodes}->{$subDomainId};

  my $cid_conf = $self->{content_transport}->addSubDomainPhostingContent( 'error_docs', $subDomainId, undef,
    "error-docs",
    "directory" => $path
  );
  $subdomainNode->getChild( 'content', 1, 1 )->addChild( $cid_conf ) if $cid_conf;

}

sub setSubDomainConfContent {
  my ( $self, $subDomainId, $path ) = @_;

  my $root = $self->{subDomainNodes}->{$subDomainId};

  my $cid_conf = $self->{content_transport}->addSubDomainPhostingContent( 'conf', $subDomainId, undef,
    "conf",
    "directory" => $path
  );
  $root->getChild( 'content', 1, 1 )->addChild( $cid_conf ) if $cid_conf;
}
###### End SubDomain functions ######

####### Mail functions #######

sub addMail {
    my ( $self, $domainId, $mailId, $mailName, $passwd, $typePasswd, $userUid, $cpAccessDefault ) = @_;

    $self->regMailnameObjectBackupPath( $self->getDomainsBackupPath( $domainId ), $mailId, $mailName, $domainId );

    my $parent = $self->getCashedDomainNode($domainId);
    return unless defined $parent;

    my $mailService = $parent->getChild('mailsystem');
    if ( not $mailService ) {
      Logging::warning("Error: Packer.addMail: there are no node 'mailsystem'", 'PackerStructure');
      return;
    }

    my $root = XmlNode->new( 'mailuser', 'attributes' => {
        'id'   => $mailId,
        'name' => $mailName
        } );

    if (defined $userUid) {
      $root->setAttribute('user-guid', $userUid);
    }
    
    if (defined $cpAccessDefault) {
      $root->setAttribute('cp-access-default', $cpAccessDefault);
    }

    my $parentGuid = $parent->getAttribute('guid');
    $root->setAttribute( 'guid', $self->getMailnameGuid( $parentGuid, $mailId ) ) if $parentGuid;
    $root->setAttribute( 'owner-guid', $parentGuid ) if $parentGuid;

    my $props = $root->getChild( 'properties', 1 );
    $root->getChild( 'preferences', 1 );

    my $item;
    if ( defined($passwd) && $passwd ) {
      if ( $passwd =~ /NULL/ ) {
        $item = CommonPacker::makePasswordNode( '', 'plain' );
      }
      else {
        $item = CommonPacker::makePasswordNode( $passwd, $typePasswd );
      }

      $props->addChild( $item );
    }

    $self->{mailNodes}->{$mailId} = $root;

    $mailService->getChild( 'mailusers', 1 )->addChild($root);
}

sub setMailBoxQuota {
    my ( $self, $mailId, $mbox_quota ) = @_;

    my $root = $self->{mailNodes}->{$mailId};

    $root->setAttribute( 'mailbox-quota', $mbox_quota );
}

sub setMailBox {
    my ( $self, $mailId, $mailName, $domainAsciiName, $enabled, $path ) = @_;

    my $root = $self->{mailNodes}->{$mailId};
    if ( !defined($root) ) {
      Logging::warning('Unable to dump $mailName mailbox settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $item = XmlNode->new( 'mailbox', 'attributes' => { 'type' => 'mdir' } );
    $root->getChild( 'preferences', 1 )->addChild($item);

    $item->setAttribute( 'enabled', $enabled );

    my $cid = $self->{content_transport}->addMailnameContent( 'mailbox', $mailId, "mdir",
      'directory' => $path, 'include_hidden_files' => 1 );
    $item->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;
}

sub addMailAliase {
    my ( $self, $mailId, $name ) = @_;

    my $root = $self->{mailNodes}->{$mailId};
    if ( !defined($root) ) {
      Logging::warning('Unable to dump $name mail alias settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $item = XmlNode->new( 'alias', 'content' => $name );
    $root->getChild( 'preferences', 1 )->addChild($item);
}

sub setMailForwarding {
    my ( $self, $mailId, $enabled, $membersPtr ) = @_;

    unless ( ref($membersPtr) =~ /ARRAY/ ) {
      Logging::warning("Error: setMailForwarding: membersPtr is not ref array",'assert');
      return;
    }

    my @members = @{$membersPtr};

    my $root = $self->{mailNodes}->{$mailId};
    if ( !defined($root) ) {
      Logging::warning('Unable to dump mail forwarding settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    $root->setAttribute( 'forwarding-enabled', $enabled? 'true': 'false' );

    my $item;
    foreach my $member (@members) {
      $item = XmlNode->new( 'forwarding', 'content' => $member );
      $root->getChild( 'preferences', 1 )->addChild($item);
    }
}

sub setMailAutoresponders {
    my ( $self, $mailId, $mailName, $domainAsciiName, $dir, $enabled, $autorespondersPtr, $attachesPtr ) = @_;

    unless ( ref($autorespondersPtr) =~ /ARRAY/ ) {
      Logging::warning("Error: setMailAutoresponders: autorespondersPtr is not ref array",'assert');
      return;
    }
    my @autoresponders = @{$autorespondersPtr};

    unless ( ref($attachesPtr) =~ /ARRAY/ ) {
      Logging::warning("Error: setMailAutoresponders: attachesPtr is not ref array",'assert');
      return;
    }
    my @attaches = @{$attachesPtr};

    my $root = $self->{mailNodes}->{$mailId};
    if ( !defined($root) ) {
      Logging::warning('Error: setMailAutoresponders: empty parent node', 'PackerStructure');
      return;
    }

    my $autorespondersNode = XmlNode->new( 'autoresponders' );

    my $cid = $self->{content_transport}->addMailnameContent( 'attaches', $mailId,  "attach",
      "directory" => $dir, 'checkEmptyDir' => 1 );
    $autorespondersNode->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;

    foreach my $file (@attaches) {
      $autorespondersNode->addChild(
        XmlNode->new( 'attach', 'attributes' => { 'file' => $file } ) );
    }

    foreach my $autoresponder (@autoresponders) {
      my $node = $self->makeAutoresponderNode ($enabled, $autoresponder, $mailName);
      $autorespondersNode->addChild($node);
    }

    $root->getChild( 'preferences', 1 )->addChild($autorespondersNode);
}

sub setMailAddressbook {
    my ( $self, $mailId, $paramsPtr, $turbaVersion ) = @_;

    my @params = @{$paramsPtr};

    my $parent = $self->{mailNodes}->{$mailId};
    if ( !defined($parent) ) {
      Logging::warning('Unable to dump mailbox addressbook, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $node = XmlNode->new('addressbook');

    my %objectFields = (
      'id'           => 'id',
      'name'         => 'name',
      'alias'        => 'alias',
      'email'        => 'email',
      'title'        => 'title',
      'company'      => 'company',
      'home-phone'   => 'homephone',
      'work-phone'   => 'workphone',
      'mobile-phone' => 'cellphone',
      'fax'          => 'fax',
      'home-address' => 'homeaddress',
      'work-address' => 'workaddress',
      'notes'        => 'notes'
      );

    if ($turbaVersion->{'majorVersion'} = 2 && $turbaVersion->{'minorVersion'} > 2 ) {
        $objectFields{'name'} = 'lastname';
        $objectFields{'home-address'} = 'homestreet';
        $objectFields{'work-address'} = 'workstreet';
        $objectFields{'first-name'} = 'firstname';
        $objectFields{'middle-names'} = 'middlenames';
    }

    foreach my $ptrHash (@params) {
      if ( $ptrHash->{'object_type'} eq 'Group' ) {

        my $group =
          XmlNode->new( 'addressbook-group',
          'attributes' => { 'id' => $ptrHash->{'object_id'} } );

        my $members = $ptrHash->{'object_members'};
        while ( $members =~ /"(.*?)"/g ) {
          my $member = $1;
          chomp $member;

          $group->addChild(
            XmlNode->new(
              'addressbook-member', 'attributes' => { 'id' => $member }
            )
          );
        }

        $node->addChild($group);
      }
      else {
        my $user = XmlNode->new('addressbook-contact');
        my ( $field, $attr );
        while ( ( $field, $attr ) = each %objectFields ) {
          if ( defined $ptrHash->{ 'object_' . $attr } ) {

            # Here 'encoded' attribute means there is no meaning to
            # convert data to UTF as it was already converted manually
            my $value = $ptrHash->{ 'object_' . $attr };
            $value = $self->{base64}->{'ENCODE'}->($value) if $attr ne 'id';
            $user->setAttribute( $field, $value );
          }
        }
        $node->addChild($user);
      }
    }

    $parent->getChild( 'preferences', 1 )->addChild($node);
}

sub setMailSpamSettings {
    my ( $self, $mailId, $mailName, $domainAsciiName, $status, $server_conf,
      $action, $requiredScore, $modify_subj, $subj_text,
      $blackListPtr, $whiteListPtr, $unblackListPtr, $unwhiteListPtr, $path, $server ) = @_;

    my @blackList = @{$blackListPtr};
    my @whiteList = @{$whiteListPtr};
    my @unblackList = @{$unblackListPtr};
    my @unwhiteList = @{$unwhiteListPtr};

    my $parent;
    if (defined($mailId)) {
    	$parent = $self->{mailNodes}->{$mailId};
    }elsif (defined($server)) {
    	$parent = $self->{serverNode};
    }
    if ( !defined($parent) ) {
      Logging::warning('Unable to dump SPAM filter settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $item = XmlNode->new( 'spamassassin' );

    $item->setAttribute( 'status', $status ) if defined($status);
    $item->setAttribute( 'action', $action ) if ( defined($action) && $action );
    $item->setAttribute( 'server-conf', $server_conf )
      if ( defined($server_conf) && $server_conf );
    $item->setAttribute( 'hits', $requiredScore )
      if ( defined($requiredScore) && $requiredScore );
    $item->setAttribute( 'modify-subj', $modify_subj )
      if ( defined($modify_subj) && $modify_subj );
    $item->setAttribute( 'subj-text', $subj_text )
      if ( defined($subj_text) && $subj_text );
    $item->setAttribute( 'max-spam-threads', $server->{'spamfilter_max_children'})
      if ( defined($server->{'spamfilter_max_children'}) && $server->{'spamfilter_max_children'});

    foreach my $blMail (@blackList) {
      $item->addChild( XmlNode->new( 'blacklist-member', 'content' => $blMail ) );
    }
    foreach my $wtMail (@whiteList) {
      $item->addChild( XmlNode->new( 'whitelist-member', 'content' => $wtMail ) );
    }
    foreach my $ublMail (@unblackList) {
      $item->addChild(
        XmlNode->new( 'unblacklist-member', 'content' => $ublMail ) );
    }
    foreach my $uwtMail (@unwhiteList) {
      $item->addChild(
        XmlNode->new( 'unwhitelist-member', 'content' => $uwtMail ) );
    }

    if (defined($mailId) && -d "$path") {
      my $cid = $self->{content_transport}->addMailnameContent( 'spam-assasin', $mailId, "sa", "directory" => $path );
      $item->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;
    }

    if (defined($mailId)) {
	    $parent->getChild( 'preferences', 1 )->addChild($item);
    }elsif(defined($server)) {
    	    $parent->addChild($item);
    }
}

sub setMailVirusSettings {
    my ( $self, $mailId, $state ) = @_;

    my $parent = $self->{mailNodes}->{$mailId};
    if ( !defined($parent) ) {
      Logging::warning('Unable to dump virus protection settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $item = XmlNode->new('virusfilter');
    $item->setAttribute( 'state', $state );

    $parent->getChild( 'preferences', 1 )->addChild($item);
}

sub addMailCustomButton {
    my ( $self, $mailId, $id, $optionsPtr, $customButtonsDir, $icon ) = @_;

    my $parent = $self->{mailNodes}->{$mailId};
    if ( !defined($parent) ) {
      Logging::warning('Unable to dump mail custom buttons settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $node = $self->makeCustomButtonNode( 'mails', $mailId, $id, $optionsPtr, $customButtonsDir, $icon );

    $parent->getChild( 'preferences', 1 )->addChild($node);
}
####### End Mail functions #######

####### Siteapps functions #######

sub addDomainSapp {
  my ( $self, $domainId, $sappId, $sapp, $licenseType ) = @_;
  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  my $phosting = $parent->getChild('phosting');
  if ( not $phosting ) {
    Logging::warning('Unable to dump domain site applications, because of dump XML structure is not full','assert');
    return;
  }

  my $sapp_installed = $self->makeSappNode($sappId, $sapp, $licenseType);

  $self->{sappNodes}->{$sappId} = $sapp_installed;
  $phosting->getChild( 'applications', 1 )->addChild($sapp_installed);
}

sub addSubDomainSapp {
  my ( $self, $subDomainId, $sappId, $sapp, $licenseType ) = @_;
  my $root = $self->{subDomainNodes}->{$subDomainId};

  my $sapp_installed = $self->makeSappNode( $sappId, $sapp, $licenseType );

  $self->{sappNodes}->{$sappId} = $sapp_installed;
  $root->getChild( 'applications', 1 )->addChild($sapp_installed);
}

sub setSappParams {
  my ( $self, $sappId, $sapp ) = @_;

  my $paramsPtr = $sapp->getParams();

  my %params = %{$paramsPtr};

  my $sapp_installed = $self->{sappNodes}->{$sappId};

  while ( my ( $param_name, $param_value ) = each %params )
  {
    my $name;
    my $value;
    if ( $sapp->encodedParams() ) {
      $name = $param_name;
      $value = $param_value;
    }
    else {
      $name = $self->{base64}->{'ENCODE'}->($param_name);
      $value = $self->{base64}->{'ENCODE'}->($param_value);
    }

    $sapp_installed->addChild(
      XmlNode->new(
        'sapp-param',
        'children' => [
          XmlNode->new(
            'sapp-param-name',
            'attributes' => { 'encoding' => 'base64' },
            'content'    => $name
          ),
          XmlNode->new(
            'sapp-param-value',
            'attributes' => { 'encoding' => 'base64' },
            'content'    => $value
          )
        ]
      )
    );
  }
}

sub setSappSettings {
  my ( $self, $sappId, $sapp ) = @_;

  my $settingsPtr = $sapp->getSettings();

  if (scalar keys %{$settingsPtr} > 0) {

    my %settings = %{$settingsPtr};

    my $sappInstalledNode = $self->{sappNodes}->{$sappId};

    my $sappSettingsNode = XmlNode->new('sapp-settings');

    while ( my ( $setting_name, $setting_value ) = each %settings )
    {
      $sappSettingsNode->addChild(
        XmlNode->new(
          'setting',
          'children' => [
            XmlNode->new(
              'name',
              'content'    => $setting_name
            ),
            XmlNode->new(
              'value',
              'content'    => $setting_value
            )
          ]
        )
      );
    }
    $sappInstalledNode->addChild($sappSettingsNode);
  }
}

sub setSappWordPressInstance {
  my ( $self, $sappId, $sapp, $wpInstance, $wpInstancePropertiesPtr ) = @_;

  my @wpInstanceProperties = @{$wpInstancePropertiesPtr};
  my $sappInstalledNode = $self->{sappNodes}->{$sappId};
  my $wpInstanceNode = XmlNode->new("wordpress-instance");
  $wpInstanceNode->setAttribute( 'path', $wpInstance->{'path'} );
  $wpInstanceNode->setAttribute( 'is-ignored', $wpInstance->{'isIgnored'} );

  if ( @wpInstanceProperties ) {
    foreach my $property (@wpInstanceProperties) {
      $wpInstanceNode->addChild(
        XmlNode->new(
          'property',
          'children' => [
            XmlNode->new(
              'name', 'content' => $property->{'name'}
            ),
            XmlNode->new(
              'value', 'content' => $property->{'value'}
            )
          ]
        )
      );
    }
  }

  $sappInstalledNode->addChild($wpInstanceNode);
}

sub setSappEntryPoints {
  my ( $self, $sappId, $sapp ) = @_;

  my $sapp_installed = $self->{sappNodes}->{$sappId};

  my @ep = $sapp->getEntryPoints();

  foreach my $node (@ep) {
    my $epNode = XmlNode->new('sapp-entry-point');
    $epNode->addChild(XmlNode->new('label', 'content' => $node->{'label'}));
    $epNode->addChild(XmlNode->new('description', 'content' => $node->{'description'}));
    $epNode->addChild(XmlNode->new('hidden', 'content' => $node->{'hidden'}));
    $epNode->addChild(XmlNode->new('http', 'content' => $node->{'http'}));
    $epNode->addChild(XmlNode->new('icon', 'content' => $node->{'icon'})) if defined $node->{'icon'};

    my $permissionsNode = XmlNode->new('limits-and-permissions');
    $permissionsNode->addChild(
      XmlNode->new('permission', 'attributes' => {'name' => $node->{'permission-name'} , 'class' => $node->{'permission-class'}})
    );
    $epNode->addChild($permissionsNode);
    $sapp_installed->addChild($epNode);
  }
}

sub setDomainSappBackupArchiveContent {
  my ( $self, $domainId, $sappId, $path, $include, $webspaceId ) = @_;
  my $sapp_installed = $self->{sappNodes}->{$sappId};
  my $cid = $self->{content_transport}->addPhostingContent( 'sapp-apsc', $domainId, $webspaceId,
    "sapp-apsc." . $sappId,
    "directory"    => $path,
    "include"      => [$include]
  );
  $sapp_installed->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;
}

sub setSubDomainSappBackupArchiveContent {
  my ( $self, $subdomainId, $sappId, $path, $include, $webspaceId ) = @_;

  my $sapp_installed = $self->{sappNodes}->{$sappId};

  my $cid = $self->{content_transport}->addSubDomainPhostingContent( 'sapp-apsc', $subdomainId, $webspaceId,
    "sapp-apsc." . $sappId,
    "directory"    => $path,
    "include"      => [$include]
  );
  $sapp_installed->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;
}


sub addDomainSappDatabase {
  my ( $self, $dbId, $dbServerId, $domainId, $sappId, $dbName, $dbType, $optionalPtr, $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent ) = @_;

  $self->regDatabaseObjectBackupPath( $self->getDomainsBackupPath( $domainId ), $dbId, $dbName, $dbServerId );
  my $parent = $self->{sappNodes}->{$sappId};

  my $domain = $self->getCashedDomainNode($domainId);

  my $node = $self->makeDatabaseNode( $dbId, $dbName, $dbType, 'domain', $domainId, $optionalPtr,
                                      $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent );
  my $domainGuid = $domain->getAttribute('guid');
  $node->setAttribute( 'guid', $self->getDatabaseGuid( $domainGuid, $dbId ) ) if $domainGuid;
  $node->setAttribute( 'owner-guid', $domainGuid ) if $domainGuid;
  $node->setAttribute( 'aps-registry-id', $optionalPtr->{'aps-registry-id'}) if $optionalPtr->{'aps-registry-id'};

  $parent->addChild($node);
}

sub addSubDomainSappDatabase {
  my ( $self, $dbId, $dbServerId, $subdomainId, $sappId, $dbName, $dbType, $optionalPtr, $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent ) = @_;

  $self->regDatabaseObjectBackupPath( $self->getSubdomainsBackupPath( $subdomainId ), $dbId, $dbName, $dbServerId );
  my $subdomain = $self->{subDomainNodes}->{$subdomainId};

  my $parent = $self->{sappNodes}->{$sappId};

  my $node = $self->makeDatabaseNode( $dbId, $dbName, $dbType, 'subdomain', $subdomainId, $optionalPtr,
                                      $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent );
  my $subDomainGuid = $subdomain->getAttribute('guid');
  $node->setAttribute( 'guid', $self->getDatabaseGuid( $subDomainGuid, $dbId ) ) if $subDomainGuid;
  $node->setAttribute( 'owner-guid', $subDomainGuid ) if $subDomainGuid;
  $node->setAttribute( 'aps-registry-id', $optionalPtr->{'aps-registry-id'}) if $optionalPtr->{'aps-registry-id'};

  $parent->addChild($node);
}

sub addDomainSappCustomButton {
    my $self = shift;
    $self->addSappCustomButton( 'domains', @_ );
}

sub addSubDomainSappCustomButton {
    my $self = shift;
    $self->addSappCustomButton( 'subdom', @_ );
}

sub addSappCustomButton {
    my ( $self, $parentType, $parentid, $sappId, $id, $optionsPtr, $customButtonsDir, $icon ) = @_;

    my $parent = $self->{sappNodes}->{$sappId};
    if ( !defined($parent) ) {
      Logging::warning('Unable to dump site applications custom buttons settings, because of dump XML structure is not full', 'PackerStructure');
      return;
    }

    my $node = $self->makeCustomButtonNode( $parentType, $parentid, $id, $optionsPtr, $customButtonsDir, $icon );

    $parent->addChild($node);
}

sub setSappApscNode {
  my ( $self, $sappId, $content) = @_;

  my $parent = $self->{sappNodes}->{$sappId};

  my $sappApscNode = XmlNode->new('sapp-apsc', 'content' => $self->{base64}->{'ENCODE'}->($content));

  $parent->addChild($sappApscNode);
}
####### End Siteapps functions #######

sub insertLimitNode {
    my ( $self, $node, $name, $value ) = @_;
    my $limits = $node->getChild( 'limits-and-permissions', 1 );
    $limits->addChild( XmlNode->new( "limit",
        "attributes" => { "name" => $name },
        "content"    => $value ) );
}

sub makePermissionNode {
  my ($self, $parent, $name, $allowed) = @_;

  #[Bug 111477] Value "-1" for attribute allowed of permission is not among the enumerated set
  if ($allowed =~ /-1/) {
   	$allowed = 'false';
  }
  $parent->addChild(  XmlNode->new('permission', 'attributes' => { 'name' => $name, 'value' => $allowed}) );
}

###### Backup content structure ######

sub getPhostingGuid{
   my ( $self, $ownerguid, $id ) = @_;
   return "$ownerguid\_ph_$id";
}

sub getDatabaseGuid{
   my ( $self, $ownerguid, $id ) = @_;
   return "$ownerguid\_db_$id";
}

sub getMailnameGuid{
   my ( $self, $ownerguid, $id ) = @_;
   return "$ownerguid\_mn_$id";
}

sub regObjectBackupPath{
 my( $self, $basePath, $group, $key, $objectId, $parentId ) = @_;
 $basePath .= '/' if $basePath;
 my $path = "$basePath$group";
 $path = $basePath if $group eq 'admin';
 $self->{backupPath}->{"$group\_$key"} = $path;
 $self->{objectId}->{"$group\_$key"} = $objectId;
 $self->{parentId}->{"$group\_$key"} = $parentId;
 return $self->getBackupPath( $group, $key, undef, 0 );
}

sub regAdminObjectBackupPath{
 my( $self, $basePath ) = @_;
 return $self->regObjectBackupPath( $basePath, 'admin', 'admin', '' );
}

sub regResellersObjectBackupPath{
 my( $self, $basePath, $key, $resellerLogin ) = @_;
 # Replace slash symbol to avoid interpretation of slash symbol as directory separator
 $resellerLogin =~ s/\//-/g;
 return $self->regObjectBackupPath( $basePath, 'resellers', $key, $self->{fnamecreator}->normalize_short_string( $resellerLogin, $key ) );
}

sub regClientObjectBackupPath{
 my( $self, $basePath, $key, $clientLogin ) = @_;
 # Replace slash symbol to avoid interpretation of slash symbol as directory separator
 $clientLogin =~ s/\//-/g;
 return $self->regObjectBackupPath( $basePath, 'clients', $key, $self->{fnamecreator}->normalize_short_string( $clientLogin, $key ) );
}

sub regDomainObjectBackupPath{
 my( $self, $basePath, $key, $domainAscName ) = @_;
 return $self->regObjectBackupPath( $basePath, 'domains', $key, $self->{fnamecreator}->normalize_long_string( $domainAscName, $key ) );
}

sub regSiteObjectBackupPath{
 my( $self, $basePath, $key, $siteName ) = @_;
 return $self->regObjectBackupPath( $basePath, 'sites', $key, $self->{fnamecreator}->normalize_long_string( $siteName, $key ) );
}

sub regMailnameObjectBackupPath{
 my( $self, $basePath, $key, $mailName, $domainId ) = @_;
 return $self->regObjectBackupPath( $basePath, 'mailnames', $key, $self->{fnamecreator}->normalize_short_string( $mailName, $key ), $domainId );
}

sub regDatabaseObjectBackupPath{
 my( $self, $basePath, $key, $databaseName, $dbServerId ) = @_;
 return $self->regObjectBackupPath( $basePath, 'databases', $key, $self->{fnamecreator}->normalize_short_string( "$databaseName\_$dbServerId", $key ) );
}

sub regSubdomainObjectBackupPath{
 my( $self, $basePath, $key, $subdomainName, $domainId ) = @_;
 return $self->regObjectBackupPath( $basePath, 'subdomains', $key, $self->{fnamecreator}->normalize_short_string( $subdomainName, $key ), $domainId );
}

sub regPhostingObjectBackupPath{
 my( $self, $basePath, $key ) = @_;
 return $self->regObjectBackupPath( $basePath, 'phosting', $key, '' );
}

sub getObjectId{
 my( $self, $group, $key ) = @_;
 $key = "$group\_$key";
 return 'phosting' if $group eq 'phosting';
 return 'admin' if $group eq 'admin';
 return $self->{objectId}->{$key};
}

sub getParentId{
 my( $self, $group, $key ) = @_;
 $key = "$group\_$key";
 return $self->{parentId}->{$key}  if exists $self->{parentId} && exists $self->{parentId}->{$key};
 return;
}

sub getPhostingParentId{
 my( $self, $key ) = @_;
 return $key;
}

sub getSubdomainParentId{
 my( $self, $key ) = @_;
 return $self->getParentId( "subdomains", $key );
}

sub getMailNameParentId{
 my( $self, $key ) = @_;
 return $self->getParentId( "mailnames", $key );
}

sub getBackupPath{
 my( $self, $group, $key, $fileName, $onlyFileName ) = @_;
 $key = "$group\_$key";
 my $res = '';
 if( not $onlyFileName ) {
     $res .=  $self->{backupPath}->{$key};
     $res .= '/' . $self->{objectId}->{$key} if $group ne 'phosting' and $group ne 'admin';
 }
 $res = $self->{fnamecreator}->getFileName( $res, $self->{backupname}, $self->{objectId}->{$key}, $fileName ) if defined $fileName;
 return $res;
}

sub getAdminBackupPath{
 my( $self, $fileName, $onlyFileName ) = @_;
 return $self->getBackupPath( 'admin', 'admin', $fileName, $onlyFileName );
}

sub getResellersBackupPath{
 my( $self, $key, $fileName, $onlyFileName ) = @_;
 return $self->getBackupPath( 'resellers', $key, $fileName, $onlyFileName );
}

sub getClientsBackupPath{
 my $self = shift;
 my $key = shift;
 return $self->getBackupPath( 'clients', $key, @_ ) if exists $self->{objectId}->{"clients_$key"};
 return $self->getResellersBackupPath( $key, @_ );
}

sub getDomainsBackupPath{
 my( $self, $key, $fileName, $onlyFileName ) = @_;
 if ( defined $self->{domainNodes}->{$key} ) {
    return $self->getBackupPath( 'domains', $key, $fileName, $onlyFileName );
 }
 return $self->getBackupPath( 'sites', $key, $fileName, $onlyFileName );
}

sub getSitesBackupPath{
 my $self = shift;
 return $self->getBackupPath( 'sites', @_ );
}

sub getPhostingBackupPath{
 my $self = shift;
 return $self->getBackupPath( 'phosting', @_ );
}

sub getDatabasesBackupPath{
 my $self = shift;
 return $self->getBackupPath( 'databases', @_ );
}

sub getMailnamesBackupPath{
 my $self = shift;
 return $self->getBackupPath( 'mailnames', @_ );
}

sub getSubdomainsBackupPath{
 my $self = shift;
 return $self->getBackupPath( 'subdomains', @_ );
}

sub getAdminObjectId{
 my( $self ) = @_;
 return $self->getObjectId( 'admin', 'admin' );
}

sub getResellerObjectId{
 my( $self, $key ) = @_;
 return $self->getObjectId( 'resellers', $key );
}

sub getClientObjectId{
 my( $self, $key ) = @_;
 return $self->getResellerObjectId( $key ) if exists $self->{objectId}->{"resellers_$key"};
 return $self->getObjectId( 'clients', $key );
}

sub getDomainObjectId{
 my( $self, $key ) = @_;
 return $self->getObjectId( 'domains', $key );
}

sub getPhostingObjectId{
 my( $self, $key ) = @_;
 return $self->getObjectId( 'phosting', $key );
}

sub getDatabaseObjectId{
 my( $self, $key ) = @_;
 return $self->getObjectId( 'databases', $key );
}

sub getMailnameObjectId{
 my( $self, $key ) = @_;
 return $self->getObjectId( 'mailnames', $key );
}

sub getSubdomainObjectId{
 my( $self, $key ) = @_;
 return $self->getObjectId( 'subdomains', $key );
}
###### End Backup content structure ######

sub addTemplateItem {
    my ( $self, $node, $name, $value ) = @_;
    $node->addChild( XmlNode->new( "template-item",
        "attributes" => { "name" => $name },
        "content"    => $value ) );
}

sub makeTemplatePlanItem {
  my ( $self, $name, $value ) = @_;

  my $templatePlanItemNode = XmlNode->new( "template-plan-item",
                                           "attributes" => { "name" => $name },
                                           "content"    => $value );
  return $templatePlanItemNode;
}

sub addTemplatePlanItem {
  my ( $self, $node, $name, $value ) = @_;

  my $templatePlanItemNode = $self->makeTemplatePlanItem( $name, $value );
  $node->addChild( $templatePlanItemNode ) if defined $templatePlanItemNode;
}

sub makeLogrotationNode {
  my ( $self, $parent, $logRotationPtr ) = @_;

  my %logRotation = %{$logRotationPtr};

  my $item = XmlNode->new( 'logrotation', 'attributes' => {
        'max-number-of-logfiles' => $logRotation{'max_number_of_logfiles'},
        'compress'               => $logRotation{'compress_enable'}
    } );

  $item->setAttribute( 'email', $logRotation{'email'} ) unless $logRotation{'email'} eq '';
  $item->setAttribute( 'enabled', $logRotation{'turned_on'} );

  my $rottype;
  if ( $logRotation{'period_type'} eq 'by_time' ) {
    $rottype = XmlNode->new( 'logrotation-period',
                             'attributes' => { 'period' => $logRotation{'period'} } );
  }
  else {
    $rottype = XmlNode->new( 'logrotation-maxsize',
                             'content' => $logRotation{'period'} );
  }
  $item->addChild($rottype);
  $parent->addChild($item);
}

sub makeDatabaseNode {
    my ( $self, $dbId, $dbName, $dbType, $parent, $parentId, $optionalPtr, $dbServerPtr, $dbUsersPtr, $contentDescriptionPtr, $skipContent ) = @_;

    my %optional = %{$optionalPtr};
    my %dbServer = %{$dbServerPtr};
    my @dbUsers = @{$dbUsersPtr};
    my %contentDescription = %{$contentDescriptionPtr};

    my $root = XmlNode->new( 'database', 'attributes' => {
        'name' => $dbName,
        'type' => $dbType,
        'id' => $dbId
        } );

    $root->setAttribute( 'version', $optional{'version'} )
      if $optional{'version'};
    $root->setAttribute( 'charset', $optional{'charset'} )
      if $optional{'charset'};
    $root->setAttribute( 'version', $optional{'version'} )
      if $optional{'version'};
    $root->setAttribute( 'db-existent', $optional{'db-existent'} )
      if $optional{'db-existent'};
    $root->setAttribute( 'prefix', $optional{'prefix'} )
      if $optional{'prefix'};
    $root->setAttribute( 'collation', $optional{'collation'} )
      if $optional{'collation'};
    $root->setAttribute( 'external-id', $optional{'external_id'} )
      if defined $optional{'external_id'} and $optional{'external_id'} ne '';

    if (%dbServer) {
      my $dbServerNode = XmlNode->new('db-server');
      $dbServerNode->setAttribute( 'type', $dbServer{'type'} );
      $dbServerNode->addChild( XmlNode->new( 'host', 'content' => $dbServer{'host'} ) );
      $dbServerNode->addChild( XmlNode->new( 'port', 'content' => $dbServer{'port'} ) );

      $root->addChild($dbServerNode);
    }

    $self->makeDatabaseUsersNodes($root, \@dbUsers, $dbId);

    if (defined($optional{'related-sites'})) {
      $self->makeRelatedSitesNode($root, $optional{'related-sites'});
    }

    if (%contentDescription and !$skipContent) {
      my $cid;
      if( $parent eq 'subdomain' ) {
        $cid = $self->{content_transport}->addSubdomainDbContent( 'sqldump', $parentId, $dbId, %contentDescription );
      }
      else{
        $cid = $self->{content_transport}->addDbContent( 'sqldump', $dbId, %contentDescription );
      }
      $root->getChild( 'content', 1, 1 )->addChild( $cid ) if $cid;
      $self->{stat}{dbSizeDumped} += $self->_getContentSizeFromCidNode($cid) if $cid;
    }

    $self->{databaseNodes}->{$dbId} = $root;
    return $root;
}

sub makeDbUsersToAnyDatabasesNode {
  my ( $self, $domainId, $dbUsersPtr ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $rootNode = $root->getChild( 'databases', 1 );

  my $globalDbUsersNode = XmlNode->new('dbusers');

  $self->makeDatabaseUsersNodes($globalDbUsersNode, $dbUsersPtr);

  $rootNode->addChild($globalDbUsersNode);
}

sub makeDatabaseUsersNodes {
    my ( $self, $parentNode, $dbUsersPtr, $dbId ) = @_;
    $dbId ||= 0;

    my @dbUsers = @{$dbUsersPtr};

    my $item;
    foreach my $user (@dbUsers) {
      my $dbUserRoot = XmlNode->new( 'dbuser',
        'attributes' => { 'name' => $user->{'login'}, 'id' => $user->{'id'} } );
 
      $dbUserRoot->setAttribute( 'default', ( defined( $user->{'default'} ) ) ? 'true' : 'false' );

      $dbUserRoot->setAttribute('aps-registry-id', $user->{'aps-registry-id'}) if $user->{'aps-registry-id'};
      $dbUserRoot->setAttribute('db-user-existent', $user->{'db-user-existent'}) if $user->{'db-user-existent'};

      $dbUserRoot->setAttribute('external-id', $user->{'external_id'}) if defined $user->{'external_id'} and $user->{'external_id'} ne '';

      $item = CommonPacker::makePasswordNode( $user->{'password'}, CommonPacker::normalizePasswordType( $user->{'type'} ) );

      $dbUserRoot->addChild($item);

      if ($user->{'host'}) {
        my %dbServer;
        $dbServer{'host'} = $user->{'host'};
        $dbServer{'port'} = $user->{'port'};
        $dbServer{'type'} = $user->{'dbservertype'};

        my $dbServerNode = $self->makeDbServerNodeWithoutCredentials(\%dbServer);
        $dbUserRoot->addChild($dbServerNode);
      }
      if (PleskVersion::atLeast(11, 6, 2)) {
          my $databaseUserRemoteAccessRulesNode = $self->makeDatabaseUserRemoteAccessRulesNode($user, $dbId);
          if ($databaseUserRemoteAccessRulesNode) {
              $dbUserRoot->addChild($databaseUserRemoteAccessRulesNode);
          }
      }

      if ( exists $user->{'acl'} ) {
        my $aclNode = XmlNode->new( 'acl' );
        foreach my $acl ( @{$user->{'acl'}} ) {
          $aclNode->addChild( XmlNode->new( 'allowed-host', 'content' => $acl ) );
        }
        $dbUserRoot->addChild( $aclNode );
      }

      if (exists($user->{'privileges'})) {
        my $privilegesNode = XmlNode->new('privileges');
        foreach my $privilege (@{$user->{'privileges'}}) {
          $privilegesNode->addChild(XmlNode->new('privilege', 'attributes' =>  $privilege));
        }
        $dbUserRoot->addChild($privilegesNode);
      }

      $parentNode->addChild($dbUserRoot);
    }
}

sub makeRelatedSitesNode {
    my ($self, $parentNode, $relatedSites) = @_;

    my $relatedSitesNode = XmlNode->new('related-sites');
    foreach my $relatedSiteName (@{$relatedSites}) {
      my $relatedSiteNode = XmlNode->new('related-site');
      $relatedSiteNode->setAttribute('name', $relatedSiteName);
      $relatedSitesNode->addChild($relatedSiteNode);
    }
    $parentNode->addChild($relatedSitesNode);
}

sub makeAutoresponderNode {
    my ( $self, $enabled, $autoPtr, $mailName ) = @_;

    my %auto = %{$autoPtr};

    my $root = XmlNode->new('autoresponder');

    my $value = $self->{base64}->{'ENCODE'}->( $auto{'text'} );

    my $item = XmlNode->new( 'text', 'content' => $value );
    if ( exists $auto{'charset'} && $auto{'charset'} ne '' ) {
      $item->setAttribute( 'charset', $auto{'charset'} );
    }
    $root->addChild($item);

    if ( exists $auto{'content_type'} && $auto{'content_type'} ) {
      $root->setAttribute( 'content-type', $auto{'content_type'} );
    }

    $root->setAttribute( 'status', ($enabled eq 'true' && $auto{'resp_on'} eq 'true') ? 'on' : 'off' );

    if ( $auto{'subject'} ) {
      $value = $self->{base64}->{'ENCODE'}->( $auto{'subject'} );
      chomp $value;
      $root->setAttribute( 'subject', $value );
    }

    #
    # forward
    #
    if ( $auto{'redirect'} ) {
      $root->setAttribute( 'redirect', $auto{'redirect'} );
    }

    #
    # end forward
    #

    if (exists($auto{'ans_freq'})) {
      $root->addChild( XmlNode->new( 'autoresponder-limit',
          'content'    => $auto{'ans_freq'},
          'attributes' => { 'name' => 'ans-freq' } ) );
    }
    if (exists($auto{'endDate'})) {
      $root->addChild( XmlNode->new( 'autoresponder-limit',
          'content'    => $auto{'endDate'},
          'attributes' => { 'name' => 'end-date' } ) );
    }

    my @attach = @{$auto{'attach'}};
    if ( @attach ) {
      foreach my $file (@attach) {
        $root->addChild( XmlNode->new( 'attach', 'attributes' => { 'file' => $file } ) );
      }
    }

    return $root;
}

sub parseCustomButtonOptions {
    my ( $node, $options ) = @_;

    #CUSTOM_BUTTON_PUBLIC
    $node->setAttribute( 'visible-to-sublogins', $options & 128 ? 'true' : 'false' );

    #CUSTOM_BUTTON_INTERNAL
    $node->setAttribute( 'open-in-same-frame', $options & 256 ? 'true' : 'false' );

    $node->setAttribute( 'no-frame', $options & 64 ? 'true' : 'false' );

    my %options = (
      "domain-id"           => 1,       #CUSTOM_BUTTON_DOM_ID
      "domain-name"         => 32,      #CUSTOM_BUTTON_DOM_NAME
      "ftp-login"           => 512,     #CUSTOM_BUTTON_FTP_USER
      "ftp-password"        => 1024,    #CUSTOM_BUTTON_FTP_PASS
      "client-id"           => 2,       #CUSTOM_BUTTON_CL_ID
      "client-company-name" => 4,       #CUSTOM_BUTTON_CNAME
      "client-contact-name" => 8,       #CUSTOM_BUTTON_PNAME
      "client-email"        => 16,      #CUSTOM_BUTTON_EMAIL
      );

    while ( my ( $optname, $optmask ) = each %options ) {
      if ( $options & $optmask ) {
        $node->addChild(
          XmlNode->new( "url-option", "attributes" => { "name" => $optname } )
        );
      }
    }
}

sub addAdminContentProxy{
    my $self = shift;
    $self->{content_transport}->addAdminContent(@_);
}

sub addClientContentProxy{
    my $self = shift;
    $self->{content_transport}->addClientContent(@_);
}

sub addDomainContentProxy{
    my $self = shift;
    $self->{content_transport}->addDomainContent(@_);
}

sub addMailnameContentProxy{
    my $self = shift;
    $self->{content_transport}->addMailnameContent(@_);
}

sub addCustomButtonNode {
  my ( $self, $optionsPtr) = @_;

  my %options = %{$optionsPtr};

  my $node = XmlNode->new('custom-button');

  my %attributes = (
    'url'      => 'url',
    'text'     => 'text',
    'sort_key' => 'sort-priority',
    'place'    => 'interface-place',
    'conhelp'  => 'conhelp' );

  foreach my $key (keys %attributes) {
    $node->setAttribute( $attributes{$key}, $options{$key} ) if defined $options{$key};
  }

  return $node;

}

sub makeCustomButtonNode65 {
  my ( $self, $optionsPtr ) = @_;
  my $node = $self->addCustomButtonNode($optionsPtr);

  ## 128 == CUSTOM_BUTTON_PUBLIC
  parseCustomButtonOptions( $node, $optionsPtr->{'options'} | 128 );

  return $node;

}

sub makeCustomButtonNode {
  my ( $self, $parentType, $parentId, $id, $optionsPtr, $customButtonsDir, $icon ) = @_;

  my $node = $self->addCustomButtonNode($optionsPtr);
  my %options = %{$optionsPtr};

  if ( -e $customButtonsDir . "/" . $icon ) {
    if ($icon) {
      my $proc;
      if( $parentType eq 'admin' ) { $proc = \&addAdminContentProxy; }
      elsif( $parentType eq 'clients' ) { $proc = \&addClientContentProxy; }
      elsif( $parentType eq 'domains' ) { $proc = \&addDomainContentProxy; }
      elsif( $parentType eq 'mails' ) { $proc = \&addMailnameContentProxy; }
      else{
        Logging::warning( "Error: makeCustomButtonNode: unsupported parent type '$parentType' ",'assert');
      }
      if( $proc ) {
        my $cid_icon =
          ( AgentConfig::tarVersionMoreThan15() )
          ? $self->$proc(
          'icon',
          $parentId,
          $icon . "." . $id,
          "directory" => $customButtonsDir,
          "add_file"  => $icon
          )
          : $self->$proc(
          'icon',
          $parentId,
          $icon . "." . $id,
          "directory" => $customButtonsDir,
          "include"   => [$icon]
          );

        $node->getChild( 'content', 1, 1 )->addChild( $cid_icon ) if $cid_icon;
      }
    }
  }
  parseCustomButtonOptions( $node, $options{'options'} );

  # exists $options{'plan_item_name'}   <=>   (PleskVersion::atLeast( 10, 1, 0))
  if ( defined $options{'plan_item_name'} && ( '' ne $options{'plan_item_name'} ) ) {
    my $templatePlanItemNode = $self->makeTemplatePlanItem( $options{'plan_item_name'}, '' );
    $node->addChild( $templatePlanItemNode );
  }

  return $node;
}

sub makeDnsZoneParam {
  my ( $self, $name, $db_unit, $db_value ) = @_;

  my $dnsZoneParam =
    XmlNode->new( 'dns-zone-param',
    'attributes' => { 'name' => $name, 'value' => $db_value } );

  my %units = (
    'second'  => 1,
    'minutes' => 60,
    'hours'   => 3600,
    'days'    => 86400,
    'weeks'   => 604800
  );
  my $string_unit;

  foreach my $quant ( keys %units ) {
    if ( $units{$quant} == $db_unit ) {
      $string_unit = $quant;
      last;
    }
  }

  if ( $string_unit == undef ) {
    $string_unit = 'second';
  }

  $dnsZoneParam->setAttribute( 'unit', $string_unit );

  return $dnsZoneParam;
}

sub makeDnsZone {
  my ( $self, $parent, $paramsPtr, $recordsPtr, $syncWithParent ) = @_;

  my %params = %{$paramsPtr};
  my @records = @{$recordsPtr};

  my $email;
  if ( defined($params{'email'}) and (length( $params{'email'} ) != 0) ) {
    $email = $params{'email'};
  }
  else {

    # should assume some default email or something...
    $email = 'root@localhost.localdomain';
  }

  my $dnsZone = XmlNode->new( 'dns-zone',
    'attributes' => { 'email' => $email, 'type' => $params{'type'} },
    'children' => [ Status::make( $params{'status'} ) ] );

  if ( exists $params{'serial_format'} ){
     $dnsZone->setAttribute( 'serial-format', $params{'serial_format'} );
  }
  if ( defined $syncWithParent ) {
    $dnsZone->setAttribute( 'sync-with-parent', $syncWithParent );
  }

  $dnsZone->setAttribute('external-id', $params{'external_id'}) if defined $params{'external_id'} and $params{'external_id'} ne '';

  foreach my $zoneParam ( 'ttl', 'refresh', 'retry', 'expire', 'minimum' ) {
    $dnsZone->addChild(
      $self->makeDnsZoneParam(
        $zoneParam, $params{ $zoneParam . '_unit' },
        $params{$zoneParam}
      )
    );
  }

  # dns records
  for my $hash (@records) {
    my $dnsrec = $self->makeDnsRecord( $hash );
    if ($dnsrec) {
      $dnsZone->addChild($dnsrec);
    }
  }

  $parent->addChild($dnsZone);
}

sub makeDnsRecord {
  my ( $self, $hashPtr ) = @_;

  unless (ref($hashPtr) =~ /HASH/)
  {
    Logging::warning('Error: makeDnsRecord: hashPtr is not hash ptr', 'assert');
    return;
  }
  my %hash = %{$hashPtr};

  return unless $hash{'type'} =~ /^A|AAAA|NS|MX|CNAME|PTR|TXT|master|SRV|AXFR$/;

  my $host =
    defined $hash{'displayHost'}
    ? $hash{'displayHost'}
    : $hash{'host'};
  my $val =
    defined $hash{'displayVal'}
    ? $hash{'displayVal'}
    : $hash{'val'};

  if ( $hash{'type'} eq 'SRV' ) {
    $host =~ s/\.$//;
    $val  =~ s/\.$//;
  }

  my ($item) =
    XmlNode->new( 'dnsrec',
    'attributes' => { 'type' => $hash{'type'}, 'src' => $host } );
  $item->setAttribute( 'dst', $val ) if ($val);
  $item->setAttribute( 'opt', $hash{'opt'} ) if ( defined $hash{'opt'} );
  $item->setAttribute( 'status', $hash{'status'} ) if (defined $hash{'status'});
  $item->setAttribute('external-id', $hash{'external_id'}) if defined $hash{'external_id'} and $hash{'external_id'} ne '';

  return $item;
}

#
# Creates node representing pre-8.0 DNS master record
#

sub makeOldMasterRec {
  my ($ipAddress) = @_;

  return XmlNode->new(
    "dnsrec",
    "attributes" => {
      "src"  => $ipAddress,
      "type" => "master"
    }
  );
}

sub addUrlDecodedTextNode {
  my ( $parent, $name, $value ) = @_;

  $parent->addChild( XmlNode->new( $name, "content" => HelpFuncs::urlDecode($value) ) )
    if $value;
}

sub makeSysUser {
  my ( $self, $sysuserPtr ) = @_;

  my %sysuser = %{$sysuserPtr};

  #
  # attributes
  #

  my $root =
    XmlNode->new( 'sysuser',
    'attributes' => { 'name' => lc( $sysuser{'login'} ) } );
  $root->setAttribute( 'shell', $sysuser{'shell'} ) if $sysuser{'shell'} and $sysuser{'shell'} ne '/bin/false';

  my $quota;
  if ( $sysuser{'quota'} ) {
    $quota = $sysuser{'quota'};
    $root->setAttribute( 'quota', $quota ) if $quota;
  }

  if ( $sysuser{'home'} ) {
    my $home = $sysuser{'home'};
    if (exists $sysuser{'relative_path'}) {
      if (substr($home,0, length($sysuser{'relative_path'})) eq $sysuser{'relative_path'} ) {
        $home = substr($home, length($sysuser{'relative_path'}));
      }
    }
    $root->setAttribute( 'home', $home) if $home;
  }

  #
  # end attributes
  #

  $root->addChild( CommonPacker::makePasswordNode( $sysuser{'passwd'}, $sysuser{'passwdType'} ) );

  if (defined($sysuser{'scheduled-tasks'})) {
    eval {require XML::Simple; 1;};
    my $xs = XML::Simple->new(ForceArray => 1);
    my $scheduledTasksXml = $xs->XMLout($sysuser{'scheduled-tasks'}, RootName => 'scheduled-tasks');
    $scheduledTasksXml = Encode::encode('UTF-8', $scheduledTasksXml); # workaround for UTF8 symbols
    $root->addChild(XmlNode->new(undef, 'raw' => $scheduledTasksXml));
  } elsif ( $sysuser{'cron'} ) {
    my $cronNode = XmlNode->new( 'cron', 'content' => $sysuser{'cron'} );
    if ( $sysuser{'cron-encoding'} ) {
      $cronNode->setAttribute( 'encoding', $sysuser{'cron-encoding'} );
    }
    $root->addChild( $cronNode );
  }

  return $root;
}

sub makeSappNode {

  my ( $self, $sappId, $sapp, $licenseType ) = @_;

  my $name = $sapp->getName();
  my $version = $sapp->getVersion();
  my $release = $sapp->getRelease();
  my $description = $sapp->getDescription();
  my $isCommercial = $sapp->isCommercial();
  my $isIntegrated = $sapp->isIntegrated();
  my $prefix = $sapp->getInstallPrefix();
  my $isSsl = $sapp->isSsl();
  my $packageId = $sapp->getSappPackageId();

  my $sapp_installed = XmlNode->new('sapp-installed');

  #-----------------------------------------------------------------
  # sapp-spec
  #-----------------------------------------------------------------
  my $sapp_spec = XmlNode->new('sapp-spec');
  if ( $packageId ) {
    $sapp_spec->addChild(
      XmlNode->new( 'sapp-package-id', 'content' => $packageId ) );
  }

  $sapp_spec->addChild( XmlNode->new( 'sapp-name', 'content' => $name ) );
  if ( $version ) {
    $sapp_spec->addChild(
      XmlNode->new( 'sapp-version', 'content' => $version ) );
  }
  if ( $release ) {
    $sapp_spec->addChild(
      XmlNode->new( 'sapp-release', 'content' => $release ) );
  }
  if ( $description ) {
    my $desc = $self->{base64}->{'ENCODE'}->( $description );
    $sapp_spec->addChild(
      XmlNode->new(
        'sapp-description',
        'attributes' => { 'encoding' => 'base64' },
        'content'    => $desc
      )
    );
  }
  if ( $isCommercial ) {
    $sapp_spec->addChild( XmlNode->new('sapp-commercial') );
  }
  if ( $isIntegrated ) {
    $sapp_spec->addChild( XmlNode->new('sapp-integrated') );
  }

  $sapp_installed->addChild($sapp_spec);

  if ( defined($licenseType) ) {
    $sapp_installed->addChild(
      XmlNode->new(
        'sapp-installed-license-type',
        'content' => $licenseType
      )
    );
  }

  return $sapp_installed;
}

sub makeSiteAppInstalled{
  my ( $self, $sappId, $prefix, $isSsl, $apsRegistryId ) = @_;

  my $sapp_installed = $self->{sappNodes}->{$sappId};

  # sapp-installdir
  my $sapp_installdir =
    XmlNode->new( 'sapp-installdir',
    'children' => [ XmlNode->new( 'sapp-prefix', 'content' => $prefix ) ] );
  if ( $isSsl ) {
    $sapp_installdir->addChild( XmlNode->new('sapp-ssl') );
  }
  if ($apsRegistryId) {
    $sapp_installdir->addChild( XmlNode->new( 'aps-registry-id', 'content' => $apsRegistryId ));
  }
  $sapp_installed->addChild($sapp_installdir);
}

sub setSappApsControllerInfo {
  my ( $self, $sappId, $sapp ) = @_;
  my $sapp_installed = $self->{sappNodes}->{$sappId};

  if ( $sapp->getContext() ) {
    $sapp_installed->addChild( XmlNode->new( 'context', 'attributes' => { "type" =>  $sapp->getContext() } ) );
  }

  if ( $sapp->getApplicationApsRegistryId() ) {
    $sapp_installed->addChild( XmlNode->new( 'aps-registry-id', 'content' => $sapp->getApplicationApsRegistryId() ) );
  }
}

sub setSappApsLicense {
  my ( $self, $sappId, $sapp ) = @_;

  my $sapp_installed = $self->{sappNodes}->{$sappId};

  if ( $sapp->isContainLicense() ) {
    my $licenseNode = XmlNode->new('sapp-license');
    $licenseNode->addChild( XmlNode->new('aps-registry-id', 'content' => $sapp->getLicenseApsRegistryId())) if $sapp->getLicenseApsRegistryId();
    $licenseNode->addChild( XmlNode->new('license-type', 'content' => $sapp->getLicenseType()));
    $licenseNode->addChild( XmlNode->new('activation-code', 'content' => $sapp->getActivationCode()));
    $licenseNode->addChild( XmlNode->new('use-stub', 'content' => $sapp->getUseStub()));
    $sapp_installed->addChild($licenseNode);
  }
}

sub makeAnonftpPermissionNode {
  my ( $parent, $name ) = @_;

  $parent->addChild(
    XmlNode->new( 'anonftp-permission', "attributes" => { "name" => $name } ) );
}

sub makeAnonftpLimitNode {
  my ( $parent, $name, $value ) = @_;
  if ( $value != 0 ) {
    $parent->addChild(
      XmlNode->new(
        "anonftp-limit",
        "attributes" => { "name" => $name },
        "content"    => $value
      )
    );
  }
}

sub makeSystemIpNode {
  my ( $self, $parent, $ptrIp ) = @_;
  my $correctMask = HelpFuncs::blockToNum( $ptrIp->{'mask'} );
  my $md5 = PerlMD5->new();
  $md5->add(HelpFuncs::urlDecode($ptrIp->{'pvtkey'}));
  $parent->getChild( 'properties', 1 )->addChild(
    XmlNode->new(
      'system-ip',
      'attributes' => { 'certificate' => $md5->hexdigest() },
      'children'   => [
        XmlNode->new(
          'ip',
          'children' => [
            XmlNode->new( 'ip-type', 'content' => "$ptrIp->{'type'}" ),
            XmlNode->new(
              'ip-address', 'content' => $ptrIp->{'ip_address'}
            )
          ]
        ),
        XmlNode->new( 'ip-netmask',   'content' => "$correctMask" ),
        XmlNode->new( 'ip-interface', 'content' => "$ptrIp->{'iface'}" )
      ]
    )
  );
}

sub makeTemplateNode {
  my ( $self, $nodeName, $templateName, $data ) = @_;

  my ( $templateAttrs, $templatePtr, $planItemsPtr, $apsBundleFilterItemsPtr, $filterType, $phpSettingsPtr, $defaultDbServersPtr );

  $templateAttrs = $data->{'attributes'};
  $templatePtr = $data->{'data'};
  $planItemsPtr = $data->{'items'};
  $apsBundleFilterItemsPtr = $data->{'aps-filter-items'};
  $filterType = $data->{'aps-filter-type'};
  $phpSettingsPtr = $data->{'php-settings'};
  $defaultDbServersPtr = $data->{'default-db-servers'};

  my %logRotation = %{$data->{'log-rotation'}};
  my %ipPool = %{$data->{'ip-pool'}};

  my $node =
    XmlNode->new( $nodeName,
    'attributes' => { 'name' => $templateName } );

  for my $ptrRowAttrs ( @{$templateAttrs} ) {
      my ($attrName, $attrValue ) = @{$ptrRowAttrs};
      $node->setAttribute( $attrName, $attrValue );
  }

  for my $ptrRow ( @{$templatePtr} ) {
      my ( $element, $value ) = @{$ptrRow};
      $self->addTemplateItem( $node, $element, $value );
  }

  while ( my ($itemName, $itemValue) = each ( %{$planItemsPtr} ) ) {
    $self->addTemplatePlanItem( $node, $itemName, $itemValue );
  }

  if (keys %logRotation){
      $self->makeLogrotationNode( $node, \%logRotation );
  }

  if (%ipPool and scalar(keys %ipPool) > 0){
	  my $ipPoolNode = XmlNode->new( 'ip_pool' );

	  for my $ip ( keys %ipPool ) {
        $ipPoolNode->addChild(
	      $self->makeIpNode($ip, $ipPool{$ip} )
	    );
	  }

      $node->addChild($ipPoolNode);
  }

  if (@{$apsBundleFilterItemsPtr}) {
    $self->makeApsBundleFilterNode(undef, undef, $filterType, $apsBundleFilterItemsPtr, $node);
  }

  if (defined $phpSettingsPtr and $nodeName ne 'reseller-template') {
    $node->addChild($self->makePhpSettingsNode($phpSettingsPtr));
  }

  if (defined $defaultDbServersPtr and $nodeName eq 'domain-template') {
    $node->addChild($self->makeDefaultDbServersNode($defaultDbServersPtr));
  }

  return $node;
}

sub makeDomainDefaultDbServersNode {
  my ($self, $domainId, $defaultDbServersPtr) = @_;

  my $parent = $self->getCashedDomainNode($domainId);

  $parent->getChild( 'preferences', 1 )->addChild($self->makeDefaultDbServersNode($defaultDbServersPtr));

}

sub makeDefaultDbServersNode {
  my ($self, $defaultDbServersPtr) = @_;

  my $defaulDbServersNode = XmlNode->new('default-db-servers');

  foreach my $dbServer (@{$defaultDbServersPtr}) {
    $defaulDbServersNode->addChild($self->makeDbServerNodeWithoutCredentials($dbServer));
  }

  return $defaulDbServersNode;
}

sub setDomainPhpSettingsNode {
  my ($self, $domainId, $phpSettingsPtr) = @_;

  my $parent = $self->getCashedDomainNode($domainId);
  return unless defined $parent;

  if (defined $phpSettingsPtr) {
    $parent->addChild($self->makePhpSettingsNode($phpSettingsPtr));
  }
}

sub makePhpSettingsNode {
  my ($self, $phpSettingsPtr) = @_;

  my $node = XmlNode->new('php-settings');

  if (exists $phpSettingsPtr->{'notice'}) {
    $node->addChild(XmlNode->new('notice-text', 'content' => $phpSettingsPtr->{'notice'}));
    delete $phpSettingsPtr->{'notice'};
  }

  foreach my $key (keys %{$phpSettingsPtr}) {
    my $settingNode = XmlNode->new('setting');
    $settingNode->addChild(XmlNode->new('name', 'content' => $key));
    $settingNode->addChild(XmlNode->new('value', 'content' => $phpSettingsPtr->{$key}));
    $node->addChild($settingNode);
  }

  return $node;
}

sub makeIpNode {
  my ($self, $ip, $iptype) = @_;
  unless ($self->{capabilityOnlyMode}) {
    return XmlNode->new(
      'ip',
      'children' => [
        XmlNode->new( 'ip-type',    content => $iptype ),
        XmlNode->new( 'ip-address', content => $ip )
      ]
    );
  } else {
    return XmlNode->new(
      $iptype,
      'children' => [
        XmlNode->new( 'source', content => $ip ),
      ]
    );
  }
}

sub addServerVirusfilter {
  my ($self, $virusfilter) = @_;

  my $root = $self->{serverNode};

  my $content = defined($virusfilter) ? $virusfilter : 'none';

  my $virusfilterNode = XmlNode->new('virusfilter', 'content' => $content);
  $virusfilterNode->setAttribute( 'state', defined($virusfilter) ? 'inout' : 'none' );

  $root->addChild($virusfilterNode);

  return $virusfilterNode;
}

sub getSb5SitePublished {
  my ($self, $name) = @_;

  my $sb5SitePublishedUtil = AgentConfig::sb5SitePublishedUtil();
  my $cmd = $sb5SitePublishedUtil . " --is-site-builder-site-published " . $name;
  Logging::debug("Exec: $cmd");
  my $result = `$cmd`;
  chomp($result);
  my $retCode = $? >> 8;
  if( $retCode != 0 ) {
    Logging::warning( "Unable to backup Site Builder publishing status on site $name (ErrorCode: $retCode, STDOUT:$result).", 'UtilityError' );
    return;
  }
  return ($result eq 'true')? 'true' : 'false';
}

sub setSb5ServerContent {
  my ($self) = @_;
  my $root = $self->{serverNode};
  my $sb5backupUtil = AgentConfig::sb5BackupUtil();

  unless (defined $sb5backupUtil)
  {
    Logging::debug("Unable to find SiteBuilder backup utility to create server content backup.");
    return;
  }

  my $dstFile = HelpFuncs::mktemp("sb5XXXXXXXX");
  my $cmd = "$sb5backupUtil --backup --target=server_settings --log=stdout --file=$dstFile";
  my $result = `$cmd`;
  my $retCode = $? >> 8;

  Logging::debug("Backing up sitebuilder server settings:");
  Logging::debug($cmd);
  Logging::debug($result);

  if( $retCode == 0 ) {
    if( -e "$dstFile.zip") {
      my $cid = $self->{content_transport}->addAdminContent('sb5-server', undef, 'sb_server', "directory" => ".", "include"   => [$dstFile.".zip"]);
      $root->getChild('content', 1, 1)->addChild($cid) if $cid;
    }
    else {
      Logging::warning( "Sitebuilder backup was not created.", 'UtilityError' );
    }
  }
  else {
    Logging::warning( "Return code of sitebuilder bru utility: ".$retCode.". Some errors occured during sitebuilder backup. Please see psadump.log file for more information", 'UtilityError' );
  }
  unlink "$dstFile.zip" if( -e "$dstFile.zip");
}

sub setSb5DomainContent {
  my ($self, $domainId, $domainName, $uuid) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $sb5backupUtil = AgentConfig::sb5BackupUtil();
  unless (defined $sb5backupUtil)
  {
    Logging::debug("Unable to find SiteBuilder backup utility to create domain content backup.");
    return;
  }

  my $dstFile = HelpFuncs::mktemp("sb5XXXXXXXX");
  my $cmd = "$sb5backupUtil --backup --target=site --uuid=$uuid --log=stdout --file=$dstFile";
  my $result = `$cmd`;
  my $retCode = $? >> 8;

  Logging::debug("Backing up sitebuilder settings for site $domainName:");
  Logging::debug($cmd);
  Logging::debug($result);
  if( $retCode==0 ){
    if( -e "$dstFile.zip") {
      my $cid = $self->{content_transport}->addDomainContent('sb5-site', $domainId, 'sb_site', "directory" => ".", "include"   => [$dstFile.".zip"]);
      if ( $cid ) {
        my @phostings = $root->getChildren('phosting');
        if ( @phostings ) {
          $phostings[0]->getChild('content', 1, 1)->addChild($cid);
        }
        else {
          Logging::warning('Error: setSb5DomainContent: there are no node "phosting"', 'PackerStructure');
        }
      }
    }
    else {
      Logging::warning( "Sitebuilder backup was not created." , 'UtilityError' );
    }
  }
  else {
    Logging::warning( "Return code of sitebuilder bru utility: ".$retCode.". Some errors occured during sitebuilder backup. Please see psadump.log file for more information", 'UtilityError' );
  }
  unlink "$dstFile.zip" if( -e "$dstFile.zip");
}

sub setSmbSiteEditorDomainContent {
  my ($self, $domainId, $domainName ) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $backupUtil = AgentConfig::sbbackupBin();
  my $dstFile = HelpFuncs::mktemp("siteeditorXXXXXXXX");
  my $cmd = "$backupUtil --target=site --name=$domainName --log=stdout --file=$dstFile";
  my $result = `$cmd`;
  my $retCode = $? >> 8;

  Logging::debug("Backing up siteeditor settings for site $domainName:");
  Logging::debug($cmd);
  Logging::debug($result);
  if( $retCode==0 ){
    if( -e "$dstFile.zip") {
      my $cid = $self->{content_transport}->addDomainContent('sb-dump', $domainId, 'sb_dump', "directory" => ".", "include"   => [$dstFile.".zip"]);
	  if ( $cid ) {
        my @phostings = $root->getChildren('phosting');
        if ( @phostings ) {
          $phostings[0]->getChild('preferences', 1)->getChild('sb-domain', 1)->getChild('content', 1, 1)->addChild($cid);
        }
        else {
          Logging::warning('Error: setSmbSiteEditorDomainContent: there are no node "phosting"', 'PackerStructure');
        }
      }
    }
    else {
      Logging::warning( "SiteEditor backup was not created." , 'UtilityError' );
    }
  }
  else {
    Logging::warning( "Return code of SiteEditor sbbackup utility: ".$retCode.". Some errors occured during SiteEditor backup. Please see psadump.log file for more information", 'UtilityError' );
  }
  unlink "$dstFile.zip" if( -e "$dstFile.zip");
}

sub addFileSharingContent {
  my ($self, $path) = @_;
  my $root = $self->{serverNode};

  my $cid = $self->{content_transport}->addAdminContent('file-sharing', undef, 'file_sharing', 'directory' => $path, 'checkEmptyDir' => 1, 'exclude' => ['unlisted']);
  $root->getChild('content', 1, 1)->addChild($cid) if $cid;
}

sub addFileSharingPasswd {
  my ($self, $path) = @_;
  my $root = $self->{serverNode};

  my $cid = $self->{content_transport}->addAdminContent('file-sharing-passwd', undef, 'file_sharing_passwd', 'directory' => $path, 'checkEmptyDir' => 1 );
  $root->getChild('content', 1, 1)->addChild($cid) if $cid;
}

sub addServerCustomApacheTemplates {
  my ($self, $path) = @_;
  my $root = $self->{serverNode};

  my $dumpFile = $self->{content_transport}->addAdminContent('custom-apache-templates', undef, 'custom_apache_templates', 'directory' => $path);
  $root->getChild('content', 1, 1)->addChild($dumpFile) if $dumpFile;

}

sub addServerCustomHealthConfig {
  my ($self) = @_;
  my $root = $self->{serverNode};

  my $path = AgentConfig::get('PRODUCT_ROOT_D') . "/var";
  my $file = 'custom-health-config.xml';

  if ( -e "$path/$file") {
    my $dumpFile = $self->{content_transport}->addAdminContent('drilldowns-config', undef, 'dd_conf', 'directory' => $path, 'include' => [$file]);
    $root->getChild('content', 1, 1)->addChild($dumpFile) if $dumpFile;
  }
}

sub addCustomizationConfig {

  my ($self) = @_;
  my $root = $self->{serverNode};

  my $path = AgentConfig::get('PRODUCT_ROOT_D') . "/admin/conf";
  my $file = 'customizations.conf';

  if ( -e "$path/$file") {

    my $customizationNode = XmlNode->new('customization');

    my $configText = undef;

    open CONFIGFILE, $path . "/" . $file;
    binmode(CONFIGFILE);

    while (<CONFIGFILE>) {
      $configText .= $_;
    }

    close CONFIGFILE;

    $customizationNode->addChild(XmlNode->new('config', 'content' => $self->{base64}->{'ENCODE'}->($configText) ));
    $root->addChild($customizationNode);
  }
}

sub addRestrictedDomains {
  my ($self) = @_;
  my $root = $self->{serverNode};
  my @restrictedDomains = @{DAL::getRestrictedDomains()};

  if ( @restrictedDomains ) {
    my $restrictedDomainNode = XmlNode->new('restricted-domains');

    foreach my $domain (@restrictedDomains) {
        $restrictedDomainNode->addChild(XmlNode->new('hostname', 'content' => $domain->{'name'}));
    }

    $root->addChild($restrictedDomainNode);
  }
}

sub addFail2ban {
  my ($self) = @_;

  my %packages = %{DAL::getSoftwarePackages()};
  if (!exists($packages{'fail2ban'})) {
    return;
  }

  my $fail2banNode = XmlNode->new('fail2ban');
  $fail2banNode->setAttribute('version', $packages{'fail2ban'});

  my $fail2banDir = '/etc/fail2ban';
  my @includeList = ('action.d/', 'filter.d/', 'jail.d/', 'jail.conf');
  if (-e "$fail2banDir/jail.local") {
    push(@includeList, 'jail.local');
  }

  my $cid = $self->{content_transport}->addAdminContent(
    'fail2ban',
    undef,
    'fail2ban',
    'directory' => $fail2banDir,
    'include' => \@includeList,
    'skip_content' => 0,
  );
  $fail2banNode->getChild('content', 1, 1)->addChild($cid) if $cid;

  $self->{serverNode}->addChild($fail2banNode);
}

sub addModSecurityContent {
  my ($self, $rulesBaseDir, @ruleSetDirs) = @_;

  my $cid = $self->{content_transport}->addAdminContent(
    'modsecurity',
    undef,
    'modsecurity',
    'directory' => $rulesBaseDir,
    'include' => \@ruleSetDirs,
    'skip_content' => 0
  );
  $self->{serverNode}->getChild('content', 1, 1)->addChild($cid) if $cid;
}

sub dumpUnityMobileIntegration {
  my ($self, $domainId, $ptrDomParams) = @_;

  my %domParams = %{$ptrDomParams};

  if ( defined $domParams{'unity_mobile_site_dns_target'}
       and defined $domParams{'unity_mobile_site_key'}
       and defined $domParams{'unity_mobile_site_prefix'}
  ) {

    my $root = $self->getCashedDomainNode($domainId);

    my $umNode = XmlNode->new('unity-mobile-integration');
    my $siteNode = XmlNode->new('mobile-site');
    $siteNode->addChild(XmlNode->new('dns-target', 'content' => $domParams{'unity_mobile_site_dns_target'}));
    $siteNode->addChild(XmlNode->new('key', 'content' => $domParams{'unity_mobile_site_key'}));
    $siteNode->addChild(XmlNode->new('prefix', 'content' => $domParams{'unity_mobile_site_prefix'}));
    $umNode->addChild($siteNode);
    $root->getChild( 'preferences', 1 )->addChild($umNode);
  }
}

sub makeApsBundleFilterNode {
  my ($self, $parentId, $parentType, $filterType, $filterItems, $parentNode) = @_;

  my $apsBundleNode = XmlNode->new('aps-bundle');

  my $filterNode = XmlNode->new('filter', 'attributes' => { 'type' => $filterType});
  $apsBundleNode->addChild($filterNode);

  foreach my $itemHash (@{$filterItems}) {
    my ($name, $value) = each %{$itemHash};
    my $itemNode = XmlNode->new('item');
    $itemNode->addChild(XmlNode->new('name', 'content' => $name));
    $itemNode->addChild(XmlNode->new('value', 'content' => $value));
    $filterNode->addChild($itemNode);
  }

  if (!defined($parentNode)) {
    if ($parentType eq 'client') {
      $parentNode = $self->getCashedClientNode($parentId);
    }elsif ($parentType eq 'domain') {
      $parentNode = $self->getCashedDomainNode($parentId);
    }

    $parentNode->getChild('preferences', 1)->addChild($apsBundleNode);
  }else{
    $parentNode->addChild($apsBundleNode);
  }
}

sub makeSkinNode {
  my ($self, $skinName, $login, $id) = @_;
  $self->makeBrandingCid($id, $login, "-name " . $skinName, "theme-skin") if $skinName ne '';
}

sub makeBrandingThemeNode {
  my ($self, $login, $id) = @_;
  $self->makeBrandingCid($id, $login, "-vendor " . $login, "branding-theme") if $login ne '';
}

sub makeBrandingCid {
  my ($self, $id, $login, $cmdArgs, $cidType) = @_;

  my $parentNode;
  if (defined $id) {
    $parentNode = $self->{resellersNodes}->{$id};
  }else {
    $parentNode = $self->{serverNode};
  }

  my $command = AgentConfig::brandingUtil();
  my $dstFile = HelpFuncs::mktemp("brandthemeXXXXXXXX");

  if (defined $command) {
      my $cmd = $command . " --pack " . $cmdArgs . " -destination " . $dstFile;
      Logging::debug("Exec: $cmd");
      my $ret = `$cmd`;
      my $retCode = $? >> 8;
      if( $retCode!=0 and $retCode != 13){
        Logging::warning( "Cannot pack theme for vendor " . $login . "STDOUT:" . $ret,'UtilityError');
        return;
      }
      if ( -e $dstFile) {
        my $cid = undef;
        if (defined $id) {
          $cid = $self->{content_transport}->addClientContent($cidType, $id, 'branding', 'directory' => '.', 'include' => [$dstFile]);
        }else{
          $cid = $self->{content_transport}->addAdminContent($cidType, undef, 'branding', 'directory' => '.', 'include' => [$dstFile]);
        }
        if ($cid) {
          $parentNode->getChild('content', 1, 1)->addChild($cid);
        }
      }
  } else {
    Logging::warning( "Utility for dumping branding theme is unavailable ", 'UtilityError');
    unlink $dstFile if ( -e $dstFile);
    return;
  }
  unlink $dstFile if ( -e $dstFile);
}

sub addCrontabSecureSettings {
  my ($self, $params) = @_;
  my $serverNode = $self->{serverNode};
  my $serverPrefNode = $serverNode->getChild( 'server-preferences', 1 );
  $serverPrefNode->addChild(XmlNode->new('crontab-secure-shell', 'content' => $params->{'crontab_secure_shell'})) if defined $params->{'crontab_secure_shell'};
  $serverPrefNode->addChild(XmlNode->new('crontab-secure-shell-compatibility-mode', 'content' => $params->{'crontab_secure_shell_compatibility_mode'} eq 'true' ? 'true' : 'false')) if defined $params->{'crontab_secure_shell_compatibility_mode'};
}

sub addKavSettingsParam {
  my ($self, $kavSettingsNode, $param, $value) = @_;

  my %allowedParams = (
      'Check' => 1,
      'AddXHeaders' => 1,
      'AdminAddress' => 1,
      'QuarantinePath' => 1,
      'FilterByName' => 1,
      'FilterByMIME' => 1,
      'SkipByName' => 1,
      'SkipByMIME' => 1,
      'Quarantine' => 1,
      'AdminNotify' => 1,
      'AdminAction' => 1,
      'SenderNotify' => 1,
      'RecipientNotify' => 1,
      'RecipientAttachReport' => 1,
      'RecipientAction' => 1,
      'CuredAdminAction' => 1,
      'CuredRecipientAction' => 1,
      'InfectedAdminAction' => 1,
      'SuspiciousAdminAction' => 1,
      'InfectedRecipientAction' => 1,
      'SuspiciousRecipientAction' => 1,
      'FilteredRecipientAction' => 1,
      'FilteredQuarantine' => 1
    );

  if ( $allowedParams{$param} ) {
    $kavSettingsNode->addChild( XmlNode->new( 'param', 'content' => $value, 'attributes' => { 'name' => $param } ) );
  }
  return;
}

sub makeSubscriptionNode {
  my ($self, $locked, $synchronized, $custom, $externalId) = @_;

  my $subscriptionNode = XmlNode->new( 'subscription' );

  if (defined $locked) {
    $subscriptionNode->setAttribute('locked', $locked);
  }

  if (defined $custom) {
    $subscriptionNode->setAttribute('custom', $custom);
  }

  if (defined $externalId and $externalId ne '') {
    $subscriptionNode->setAttribute('external-id', $externalId);
  }

  if (defined $synchronized) {
    $subscriptionNode->setAttribute('synchronized', $synchronized);
  }

  return $subscriptionNode;
}

sub addSubscriptionPlan {
  my ($self, $subscriptionNode, $quantity, $plan_guid, $is_addon) = @_;

  return unless ( ( defined $quantity ) && ( defined $plan_guid ) );
  my $planNode = XmlNode->new( 'plan' );
  $planNode->setAttribute('quantity', $quantity);
  $planNode->setAttribute('plan-guid', $plan_guid);
  if ( defined $is_addon) {
    $planNode->setAttribute('is-addon', $is_addon);
  }
  $subscriptionNode->addChild($planNode);
  return;
}

sub dumpFileSharingServerSettings {
  my ($self, $settingsPtr) = @_;

  my %settings = %{$settingsPtr};

  my $fsNode = XmlNode->new('file-sharing-settings');

  my $parentNode = $self->{serverNode};

  foreach my $key (keys %settings) {
    $fsNode->addChild(XmlNode->new('setting','attributes' =>
      { 'name' => $key, 'value' => $settings{$key}} ) );
  }
  $parentNode->addChild($fsNode);
}

sub dumpFileSharingUnlistedFiles {
  my ($self, $unlistedFiles) = @_;

  my $unlistedFilesNode = XmlNode->new('file-sharing-unlisted-files');
  foreach my $unlistedFile (@{$unlistedFiles}) {
    $unlistedFilesNode->addChild(XmlNode->new('file-sharing-unlisted-file','attributes' => {
      'creation-date' => $unlistedFile->{'creationDate'},
      , 'salt' => $unlistedFile->{'salt'}
      , 'name' => $unlistedFile->{'name'}
      , 'path' => $unlistedFile->{'path'}
      , 'expiration' => $unlistedFile->{'expiration'}
    }));
  }
  $self->{serverNode}->addChild($unlistedFilesNode);
}

sub dumpUiMode {
  my ($self, $paramsPtr) = @_;

  my %params = %{$paramsPtr};

  my $parent = $self->{serverNode};

  my $uiMode = undef;

  if ((PleskVersion::atLeast(12, 1, 23) && (!defined($params{'power_user_panel'}) || $params{'power_user_panel'} ne 'true'))
    || (!PleskVersion::atLeast(12, 1, 23) && defined $params{'classic_panel'} && $params{'classic_panel'} eq 'true')) {
    $uiMode = 'classic';
  } else {
    if (defined $params{'simple_panel'} && $params{'simple_panel'} eq 'true') {
      $uiMode = (defined $params{'simple_panel_lock'} && $params{'simple_panel_lock'} eq 'true') ? 'simple-lock' : 'simple';
    } else {
      $uiMode = 'normal';
    }
  }

  if (defined $uiMode) {
    $parent->getChild( 'interface-preferences', 1 )->addChild(XmlNode->new('ui-mode', 'content' => $uiMode));
  }
}

sub addTechnicalPreviewDomainNode {
  my ($self, $domainName) = @_;
  my $serverNode = $self->{serverNode};
  my $serverPrefNode = $serverNode->getChild( 'server-preferences', 1 );
  $serverPrefNode->addChild(XmlNode->new('technical-domain', 'content' => $domainName));
}

sub addUpdateSettings {
  my ($self, $autoUpdatesValue, $autoUpgradeToStable, $autoUpgradeBranch) = @_;

  my $serverNode = $self->{serverNode};
  my $serverPrefNode = $serverNode->getChild( 'server-preferences', 1 );
  my $updateSettingsNode = XmlNode->new('update-settings');
  $updateSettingsNode->addChild(XmlNode->new('autoupdates', 'content' => $autoUpdatesValue));
  $updateSettingsNode->addChild(XmlNode->new('autoupgrade-stable', 'content' => $autoUpgradeToStable));
  $updateSettingsNode->addChild(XmlNode->new('release-tier', 'content' => $autoUpgradeBranch));

  $serverPrefNode->addChild($updateSettingsNode);
}

sub addFtpOverSslSettings {
  my ($self, $value) = @_;
  
  my $serverNode = $self->{serverNode};
  my $serverPrefNode = $serverNode->getChild( 'server-preferences', 1 );
  $serverPrefNode->addChild(XmlNode->new('ftp-over-ssl', 'content' => $value));
}

sub setMiscParameters {
  my ($self, $paramsPtr) = @_;

  my $serverNode = $self->{serverNode};
  my %params = %{$paramsPtr};

  my $miscNode = $serverNode->getChild( 'misc', 1 );

  foreach my $name (keys %params) {
    my $paramNode = XmlNode->new('param');
    $paramNode->addChild(XmlNode->new('name', 'content' => $name));
    $paramNode->addChild(XmlNode->new('value', 'content' => $params{$name}));
    $miscNode->addChild($paramNode);
  }
}

sub makeExtensionPackageNode {
  my ($self, $objectType, $objectId, $data) = @_;

  my ($parentNode, $dataNode);

  if ($objectType eq 'client') {
    $parentNode = $self->{clientNodes}->{$objectId};
  } elsif ($objectType eq 'reseller') {
    $parentNode = $self->{resellersNodes}->{$objectId};
  } elsif ($objectType eq 'domain') {
    $parentNode = $self->{domainNodes}->{$objectId};
  } elsif ($objectType eq 'server') {
    $parentNode = $self->{serverNode};
  }

  $dataNode = XmlNode->new('pmm-extension-package');
  $dataNode->setTextAsIs($data);
  $parentNode->addChild($dataNode);
}

sub makeCapabilityInfoNode {
  my ($self, $capabilityInfo, $objectId) = @_;

  my ($parentNode, $capabilityNode, $name, $value);

  $parentNode = $self->{domainNodes}->{$objectId};

  $capabilityNode = XmlNode->new('capability-info');

  foreach my $key (keys %{$capabilityInfo}) {
    my $info = $capabilityInfo->{$key};
    my $node = $self->_makeCapabilitySectionNode($key, $info);
    $capabilityNode->addChild($node);
  }

  $parentNode->addChild($capabilityNode);
}

sub makeServerCapabilityInfoNode {
  my ($self, $capabilityInfo) = @_;

  my $parentNode = $self->{serverNode};
  my $capabilityNode = XmlNode->new('capability-info');
  my $sectionNode;

  foreach my $key (keys %{$capabilityInfo}) {
    if ($key eq 'database-servers') {
      $sectionNode = XmlNode->new($key);
      my $servers = $capabilityInfo->{$key};
      for my $server (@$servers) {
        my $serverNode = XmlNode->new('database-server');

        my ($name, $value);
        while ( ( $name, $value ) = each(%{$server}) ) {
          $serverNode->addChild(XmlNode->new($name, 'content' => $value));
        }

        $sectionNode->addChild($serverNode);
      }
    } else {
      $sectionNode = $self->_makeCapabilitySectionNode($key, $capabilityInfo->{$key});
    }
    $capabilityNode->addChild($sectionNode);
  }

  $parentNode->addChild($capabilityNode);
}

sub _makeCapabilitySectionNode {
  my ($self, $sectionName, $values) = @_;

  my $node = XmlNode->new($sectionName);
  my ($name, $value);
  while ( ( $name, $value ) = each(%{$values}) ) {
    if (ref($value) =~ /ARRAY/) {
      foreach my $valueItem (@{$value}) {
        $node->addChild($self->_makeNameValueNode($self->_getChildNodeName($sectionName), $name, $valueItem));
      }
    } else {
      $node->addChild($self->_makeNameValueNode($self->_getChildNodeName($sectionName), $name, $value));
    }
  }

  return $node;
}

sub _getChildNodeName {
  my ($self, $parentNodeName) = @_;

  my %nodesMapping = (
    'components' => 'component'
    , 'resource-usage' => 'resource'
    , 'miscellaneous' => 'setting'
    , 'apache-modules' => 'module'
  );

  return $nodesMapping{$parentNodeName};
}

sub _makeNameValueNode {
  my ($self, $nodeName, $name, $value) = @_;

  my $node = XmlNode->new($nodeName);
  my $nameNode = XmlNode->new('name', 'content' => $name);
  my $valueNode = XmlNode->new('value', 'content' => $value);
  $node->addChild($nameNode);
  $node->addChild($valueNode);

  return $node;
}

sub makeDomainParamsNode {
  my ($self, $domainId, $domainParams) = @_;

  my $root = $self->getCashedDomainNode($domainId);
  return unless defined $root;

  my $properties = $root->getChild( 'properties', 1 );
  my $domParam = XmlNode->new('dom-param');
  foreach my $key (keys %{$domainParams}) {
    $domParam->addChild($self->_makeNameValueNode('param', $key, $self->{base64}->{'ENCODE'}->($domainParams->{$key})));
  }
  $properties->addChild($domParam);
}

sub makeSubscriptionPropertiesNode {
  my ($self, $parentNode, $subscriptionProperties) = @_;

  my $propertiesNode = XmlNode->new('properties');
  foreach my $key (keys %{$subscriptionProperties}) {
    $propertiesNode->addChild($self->_makeNameValueNode('property', $key, $self->{base64}->{'ENCODE'}->($subscriptionProperties->{$key})));
  }
  $parentNode->addChild($propertiesNode);
}

sub assignApplicationInfoToMailUser {
  my ($self, $applicationApsRegistryId, $resourceInfo) = @_;

  my $mailUserNode = $self->{mailNodes}->{$resourceInfo->{'id'}};
  return unless defined $mailUserNode;

  my $apsServices = $mailUserNode->getChild('aps-services', 1);

  my $apsService = XmlNode->new('aps-service');
  $apsService->addChild(XmlNode->new('application-registry-id', 'content' => $applicationApsRegistryId));
  $apsService->addChild(XmlNode->new('resource-registry-id', 'content' => $resourceInfo->{'apsRegistryId'}));

  $apsServices->addChild($apsService);
}

sub makeDomainDescriptionsNode {
  my ( $self, $domainId, $description ) = @_;

  my $domainNode = $self->getCashedDomainNode( $domainId );
  my $prefs = $domainNode->getChild( 'preferences', 1 );

  $self->makeDescriptionsNode( $prefs, [{ 'description' => $description }] );
}

sub makeMailUserDescriptionNode {
  my ( $self, $mailUserId, $description ) = @_;

  my $mailUserNode = $self->{mailNodes}->{$mailUserId};
  my $prefs = $mailUserNode->getChild( 'preferences', 1 );

  $self->makeDescriptionsNode( $prefs, [{ 'description' => $description }] );
}

sub makeResellerDescriptionsNode {
  my ( $self, $resellerId, $descriptions ) = @_;

  my $resellerNode = $self->{resellersNodes}->{$resellerId};

  my $prefs = $resellerNode->getChild( 'preferences', 1 );

  $self->makeDescriptionsNode( $prefs, $descriptions );
}

sub makeDescriptionsNode {
  my ( $self, $parentNode, $descriptions ) = @_;

  if ( scalar @{$descriptions} > 0 ) {
    my $descriptionsNode = XmlNode->new( 'descriptions' );
    foreach my $description ( @{$descriptions} ) {
      my $descriptionNode = XmlNode->new( 'description', 'content' => $description->{'description'} );
      $descriptionNode->setAttribute( 'object-name', $description->{'name'}) if $description->{'name'} ne '';
      $descriptionNode->setAttribute( 'object-type', $description->{'type'}) if $description->{'type'} ne '';
      $descriptionsNode->addChild( $descriptionNode );
    }
    $parentNode->addChild( $descriptionsNode );
  }
}

sub makeOutgoingMessagesParameter {
    my ($self, $objectType, $objectDbId, $paramName, $paramValue) = @_;

    my $object = $self->_getObject($objectType, $objectDbId);
    $object->getChild('preferences', 1)->getChild('outgoing-messages', 1)->addChild(XmlNode->new('parameter', 'children' => [
        XmlNode->new('name', 'content' => $paramName),
        XmlNode->new('value', 'content' => $paramValue),
    ]));
}

sub _makeSeverOutgoingEmailMode {
    my ($self, $mailSettingsPtr, $ipAddressesPtr) = @_;
    my %mailSettings = %{$mailSettingsPtr};
    my %ipAddresses = %{$ipAddressesPtr};
    if (!defined($mailSettings{'outgoing_email_mode'})) {
        return;
    }

    my $outgoingEmailMode = $mailSettings{'outgoing_email_mode'};
    Logging::debug("outgoing_email_mode found: $outgoingEmailMode");
    if ($outgoingEmailMode eq 'domain-ip' || $outgoingEmailMode eq 'domain-name') {
        $self->{serverNode}->getChild('mail-settings', 1)->addChild(XmlNode->new('outgoing-email-mode', 'children' => [
            XmlNode->new($outgoingEmailMode)
        ]));
    } elsif ($outgoingEmailMode == 'explicit-ip') {
        my @ips;
        for my $param ('outgoing_email_mode_explicit_ip_v4', 'outgoing_email_mode_explicit_ip_v6') {
            if (defined($mailSettings{$param}) && $mailSettings{$param} ne ''
                && defined($ipAddresses{$mailSettings{$param}})) {
                push @ips, XmlNode->new('ip-address', 'content' => $ipAddresses{$mailSettings{$param}}->{'ip_address'});
            }
        }
        $self->{serverNode}->getChild('mail-settings', 1)->addChild(XmlNode->new('outgoing-email-mode', 'children' => [
            XmlNode->new($outgoingEmailMode, 'children' => \@ips)
        ]));
    }
}

sub _makeServerOutgoingMessagesParameter {
    my ($self, $paramName, $paramValue) = @_;

    $self->{serverNode}->getChild('mail-settings', 1)->getChild('outgoing-messages', 1)->addChild(
        XmlNode->new('parameter', 'children' => [
            XmlNode->new('name', 'content' => $paramName),
            XmlNode->new('value', 'content' => $paramValue),
        ]
    ));
}

# Method return object by their types and id
# returns XmlNode | undef
sub _getObject{
    my ($self, $objectType, $objectDbId) = @_;

    if ($objectType eq 'admin') {
        return $self->{admin};
    } elsif ($objectType eq 'reseller') {
        return $self->{resellersNodes}->{$objectDbId};
    } elsif ($objectType eq 'client') {
        return $self->{clientNodes}->{$objectDbId};
    } elsif ($objectType eq 'domain') {
        return $self->getCashedDomainNode($objectDbId);
    } elsif ($objectType eq 'mailsystem') {
        return $self->getCashedDomainNode($objectDbId)->getChild('mailsystem', 1);
    } elsif ($objectType eq 'mailuser') {
        return $self->{mailNodes}->{$objectDbId};
    } else {
        Logging::error("Object type \"$objectType\" is not supported by Packer::_getObject");
    }
    return undef;
}

sub makeClParamNode {
  my ( $self, $clientId, $clientType, $params ) = @_;

  my $parentNode = $self->_getObject($clientType, $clientId)->getChild('properties', 1)->getChild('cl-param', 1);
  foreach my $param ( keys( %{$params} ) ) {
    my $paramNode = XmlNode->new( 'param' );
    $paramNode->addChild( XmlNode->new( 'name', 'content' => $param ) );
    $paramNode->addChild( XmlNode->new( 'value', 'content' => $self->{base64}->{'ENCODE'}->( $params->{$param} ) ) );
    $parentNode->addChild( $paramNode );
  }
}

1;
